1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 */ 4 /* 5 * Licensed to the Apache Software Foundation (ASF) under one or more 6 * contributor license agreements. See the NOTICE file distributed with 7 * this work for additional information regarding copyright ownership. 8 * The ASF licenses this file to You under the Apache License, Version 2.0 9 * (the "License"); you may not use this file except in compliance with 10 * the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package com.sun.org.apache.xerces.internal.dom; 22 23 import java.util.ArrayList; 24 import java.util.List; 25 import org.w3c.dom.CharacterData; 26 import org.w3c.dom.DOMException; 27 import org.w3c.dom.DocumentFragment; 28 import org.w3c.dom.Node; 29 import org.w3c.dom.ranges.Range; 30 import org.w3c.dom.ranges.RangeException; 31 32 33 /** The RangeImpl class implements the org.w3c.dom.range.Range interface. 34 * <p> Please see the API documentation for the interface classes 35 * and use the interfaces in your client programs. 36 * 37 * @xerces.internal 38 * 39 * @LastModified: Oct 2017 40 */ 41 public class RangeImpl implements Range { 42 43 // 44 // Constants 45 // 46 47 48 // 49 // Data 50 // 51 52 DocumentImpl fDocument; 53 Node fStartContainer; 54 Node fEndContainer; 55 int fStartOffset; 56 int fEndOffset; 57 boolean fIsCollapsed; 58 boolean fDetach = false; 59 Node fInsertNode = null; 60 Node fDeleteNode = null; 61 Node fSplitNode = null; 62 // Was the Node inserted from the Range or the Document 63 boolean fInsertedFromRange = false; 64 65 /** The constructor. Clients must use DocumentRange.createRange(), 66 * because it registers the Range with the document, so it can 67 * be fixed-up. 68 */ 69 public RangeImpl(DocumentImpl document) { 70 fDocument = document; 71 fStartContainer = document; 72 fEndContainer = document; 73 fStartOffset = 0; 74 fEndOffset = 0; 75 fDetach = false; 76 } 77 78 public Node getStartContainer() { 79 if ( fDetach ) { 80 throw new DOMException( 81 DOMException.INVALID_STATE_ERR, 82 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 83 } 84 return fStartContainer; 85 } 86 87 public int getStartOffset() { 88 if ( fDetach ) { 89 throw new DOMException( 90 DOMException.INVALID_STATE_ERR, 91 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 92 } 93 return fStartOffset; 94 } 95 96 public Node getEndContainer() { 97 if ( fDetach ) { 98 throw new DOMException( 99 DOMException.INVALID_STATE_ERR, 100 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 101 } 102 return fEndContainer; 103 } 104 105 public int getEndOffset() { 106 if ( fDetach ) { 107 throw new DOMException( 108 DOMException.INVALID_STATE_ERR, 109 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 110 } 111 return fEndOffset; 112 } 113 114 public boolean getCollapsed() { 115 if ( fDetach ) { 116 throw new DOMException( 117 DOMException.INVALID_STATE_ERR, 118 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 119 } 120 return (fStartContainer == fEndContainer 121 && fStartOffset == fEndOffset); 122 } 123 124 public Node getCommonAncestorContainer() { 125 if ( fDetach ) { 126 throw new DOMException( 127 DOMException.INVALID_STATE_ERR, 128 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 129 } 130 List<Node> startV = new ArrayList<>(); 131 Node node; 132 for (node=fStartContainer; node != null; 133 node=node.getParentNode()) 134 { 135 startV.add(node); 136 } 137 List<Node> endV = new ArrayList<>(); 138 for (node=fEndContainer; node != null; 139 node=node.getParentNode()) 140 { 141 endV.add(node); 142 } 143 int s = startV.size()-1; 144 int e = endV.size()-1; 145 Node result = null; 146 while (s>=0 && e>=0) { 147 if (startV.get(s) == endV.get(e)) { 148 result = startV.get(s); 149 } else { 150 break; 151 } 152 --s; 153 --e; 154 } 155 return result; 156 } 157 158 159 public void setStart(Node refNode, int offset) 160 throws RangeException, DOMException 161 { 162 if (fDocument.errorChecking) { 163 if ( fDetach) { 164 throw new DOMException( 165 DOMException.INVALID_STATE_ERR, 166 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 167 } 168 if ( !isLegalContainer(refNode)) { 169 throw new RangeExceptionImpl( 170 RangeException.INVALID_NODE_TYPE_ERR, 171 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 172 } 173 if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { 174 throw new DOMException( 175 DOMException.WRONG_DOCUMENT_ERR, 176 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 177 } 178 } 179 180 checkIndex(refNode, offset); 181 182 fStartContainer = refNode; 183 fStartOffset = offset; 184 185 // If one boundary-point of a Range is set to have a root container 186 // other 187 // than the current one for the Range, the Range should be collapsed to 188 // the new position. 189 // The start position of a Range should never be after the end position. 190 if (getCommonAncestorContainer() == null 191 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { 192 collapse(true); 193 } 194 } 195 196 public void setEnd(Node refNode, int offset) 197 throws RangeException, DOMException 198 { 199 if (fDocument.errorChecking) { 200 if (fDetach) { 201 throw new DOMException( 202 DOMException.INVALID_STATE_ERR, 203 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 204 } 205 if ( !isLegalContainer(refNode)) { 206 throw new RangeExceptionImpl( 207 RangeException.INVALID_NODE_TYPE_ERR, 208 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 209 } 210 if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { 211 throw new DOMException( 212 DOMException.WRONG_DOCUMENT_ERR, 213 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 214 } 215 } 216 217 checkIndex(refNode, offset); 218 219 fEndContainer = refNode; 220 fEndOffset = offset; 221 222 // If one boundary-point of a Range is set to have a root container 223 // other 224 // than the current one for the Range, the Range should be collapsed to 225 // the new position. 226 // The start position of a Range should never be after the end position. 227 if (getCommonAncestorContainer() == null 228 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { 229 collapse(false); 230 } 231 } 232 233 public void setStartBefore(Node refNode) 234 throws RangeException 235 { 236 if (fDocument.errorChecking) { 237 if (fDetach) { 238 throw new DOMException( 239 DOMException.INVALID_STATE_ERR, 240 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 241 } 242 if ( !hasLegalRootContainer(refNode) || 243 !isLegalContainedNode(refNode) ) 244 { 245 throw new RangeExceptionImpl( 246 RangeException.INVALID_NODE_TYPE_ERR, 247 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 248 } 249 if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { 250 throw new DOMException( 251 DOMException.WRONG_DOCUMENT_ERR, 252 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 253 } 254 } 255 256 fStartContainer = refNode.getParentNode(); 257 int i = 0; 258 for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { 259 i++; 260 } 261 fStartOffset = i-1; 262 263 // If one boundary-point of a Range is set to have a root container 264 // other 265 // than the current one for the Range, the Range should be collapsed to 266 // the new position. 267 // The start position of a Range should never be after the end position. 268 if (getCommonAncestorContainer() == null 269 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { 270 collapse(true); 271 } 272 } 273 274 public void setStartAfter(Node refNode) 275 throws RangeException 276 { 277 if (fDocument.errorChecking) { 278 if (fDetach) { 279 throw new DOMException( 280 DOMException.INVALID_STATE_ERR, 281 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 282 } 283 if ( !hasLegalRootContainer(refNode) || 284 !isLegalContainedNode(refNode)) { 285 throw new RangeExceptionImpl( 286 RangeException.INVALID_NODE_TYPE_ERR, 287 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 288 } 289 if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { 290 throw new DOMException( 291 DOMException.WRONG_DOCUMENT_ERR, 292 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 293 } 294 } 295 fStartContainer = refNode.getParentNode(); 296 int i = 0; 297 for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { 298 i++; 299 } 300 fStartOffset = i; 301 302 // If one boundary-point of a Range is set to have a root container 303 // other 304 // than the current one for the Range, the Range should be collapsed to 305 // the new position. 306 // The start position of a Range should never be after the end position. 307 if (getCommonAncestorContainer() == null 308 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { 309 collapse(true); 310 } 311 } 312 313 public void setEndBefore(Node refNode) 314 throws RangeException 315 { 316 if (fDocument.errorChecking) { 317 if (fDetach) { 318 throw new DOMException( 319 DOMException.INVALID_STATE_ERR, 320 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 321 } 322 if ( !hasLegalRootContainer(refNode) || 323 !isLegalContainedNode(refNode)) { 324 throw new RangeExceptionImpl( 325 RangeException.INVALID_NODE_TYPE_ERR, 326 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 327 } 328 if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { 329 throw new DOMException( 330 DOMException.WRONG_DOCUMENT_ERR, 331 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 332 } 333 } 334 fEndContainer = refNode.getParentNode(); 335 int i = 0; 336 for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { 337 i++; 338 } 339 fEndOffset = i-1; 340 341 // If one boundary-point of a Range is set to have a root container 342 // other 343 // than the current one for the Range, the Range should be collapsed to 344 // the new position. 345 // The start position of a Range should never be after the end position. 346 if (getCommonAncestorContainer() == null 347 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { 348 collapse(false); 349 } 350 } 351 352 public void setEndAfter(Node refNode) 353 throws RangeException 354 { 355 if (fDocument.errorChecking) { 356 if( fDetach) { 357 throw new DOMException( 358 DOMException.INVALID_STATE_ERR, 359 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 360 } 361 if ( !hasLegalRootContainer(refNode) || 362 !isLegalContainedNode(refNode)) { 363 throw new RangeExceptionImpl( 364 RangeException.INVALID_NODE_TYPE_ERR, 365 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 366 } 367 if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { 368 throw new DOMException( 369 DOMException.WRONG_DOCUMENT_ERR, 370 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 371 } 372 } 373 fEndContainer = refNode.getParentNode(); 374 int i = 0; 375 for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { 376 i++; 377 } 378 fEndOffset = i; 379 380 // If one boundary-point of a Range is set to have a root container 381 // other 382 // than the current one for the Range, the Range should be collapsed to 383 // the new position. 384 // The start position of a Range should never be after the end position. 385 if (getCommonAncestorContainer() == null 386 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { 387 collapse(false); 388 } 389 } 390 391 public void collapse(boolean toStart) { 392 393 if( fDetach) { 394 throw new DOMException( 395 DOMException.INVALID_STATE_ERR, 396 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 397 } 398 399 if (toStart) { 400 fEndContainer = fStartContainer; 401 fEndOffset = fStartOffset; 402 } else { 403 fStartContainer = fEndContainer; 404 fStartOffset = fEndOffset; 405 } 406 } 407 408 public void selectNode(Node refNode) 409 throws RangeException 410 { 411 if (fDocument.errorChecking) { 412 if (fDetach) { 413 throw new DOMException( 414 DOMException.INVALID_STATE_ERR, 415 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 416 } 417 if ( !isLegalContainer( refNode.getParentNode() ) || 418 !isLegalContainedNode( refNode ) ) { 419 throw new RangeExceptionImpl( 420 RangeException.INVALID_NODE_TYPE_ERR, 421 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 422 } 423 if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { 424 throw new DOMException( 425 DOMException.WRONG_DOCUMENT_ERR, 426 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 427 } 428 } 429 Node parent = refNode.getParentNode(); 430 if (parent != null ) // REVIST: what to do if it IS null? 431 { 432 fStartContainer = parent; 433 fEndContainer = parent; 434 int i = 0; 435 for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { 436 i++; 437 } 438 fStartOffset = i-1; 439 fEndOffset = fStartOffset+1; 440 } 441 } 442 443 public void selectNodeContents(Node refNode) 444 throws RangeException 445 { 446 if (fDocument.errorChecking) { 447 if( fDetach) { 448 throw new DOMException( 449 DOMException.INVALID_STATE_ERR, 450 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 451 } 452 if ( !isLegalContainer(refNode)) { 453 throw new RangeExceptionImpl( 454 RangeException.INVALID_NODE_TYPE_ERR, 455 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 456 } 457 if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { 458 throw new DOMException( 459 DOMException.WRONG_DOCUMENT_ERR, 460 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 461 } 462 } 463 fStartContainer = refNode; 464 fEndContainer = refNode; 465 Node first = refNode.getFirstChild(); 466 fStartOffset = 0; 467 if (first == null) { 468 fEndOffset = 0; 469 } else { 470 int i = 0; 471 for (Node n = first; n!=null; n = n.getNextSibling()) { 472 i++; 473 } 474 fEndOffset = i; 475 } 476 477 } 478 479 public short compareBoundaryPoints(short how, Range sourceRange) 480 throws DOMException 481 { 482 if (fDocument.errorChecking) { 483 if( fDetach) { 484 throw new DOMException( 485 DOMException.INVALID_STATE_ERR, 486 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 487 } 488 // WRONG_DOCUMENT_ERR: Raised if the two Ranges are not in the same Document or DocumentFragment. 489 if ((fDocument != sourceRange.getStartContainer().getOwnerDocument() 490 && fDocument != sourceRange.getStartContainer() 491 && sourceRange.getStartContainer() != null) 492 || (fDocument != sourceRange.getEndContainer().getOwnerDocument() 493 && fDocument != sourceRange.getEndContainer() 494 && sourceRange.getStartContainer() != null)) { 495 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, 496 DOMMessageFormatter.formatMessage( DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 497 } 498 } 499 500 Node endPointA; 501 Node endPointB; 502 int offsetA; 503 int offsetB; 504 505 if (how == START_TO_START) { 506 endPointA = sourceRange.getStartContainer(); 507 endPointB = fStartContainer; 508 offsetA = sourceRange.getStartOffset(); 509 offsetB = fStartOffset; 510 } else 511 if (how == START_TO_END) { 512 endPointA = sourceRange.getStartContainer(); 513 endPointB = fEndContainer; 514 offsetA = sourceRange.getStartOffset(); 515 offsetB = fEndOffset; 516 } else 517 if (how == END_TO_START) { 518 endPointA = sourceRange.getEndContainer(); 519 endPointB = fStartContainer; 520 offsetA = sourceRange.getEndOffset(); 521 offsetB = fStartOffset; 522 } else { 523 endPointA = sourceRange.getEndContainer(); 524 endPointB = fEndContainer; 525 offsetA = sourceRange.getEndOffset(); 526 offsetB = fEndOffset; 527 } 528 529 // The DOM Spec outlines four cases that need to be tested 530 // to compare two range boundary points: 531 // case 1: same container 532 // case 2: Child C of container A is ancestor of B 533 // case 3: Child C of container B is ancestor of A 534 // case 4: preorder traversal of context tree. 535 536 // case 1: same container 537 if (endPointA == endPointB) { 538 if (offsetA < offsetB) return 1; 539 if (offsetA == offsetB) return 0; 540 return -1; 541 } 542 // case 2: Child C of container A is ancestor of B 543 // This can be quickly tested by walking the parent chain of B 544 for ( Node c = endPointB, p = c.getParentNode(); 545 p != null; 546 c = p, p = p.getParentNode()) 547 { 548 if (p == endPointA) { 549 int index = indexOf(c, endPointA); 550 if (offsetA <= index) return 1; 551 return -1; 552 } 553 } 554 555 // case 3: Child C of container B is ancestor of A 556 // This can be quickly tested by walking the parent chain of A 557 for ( Node c = endPointA, p = c.getParentNode(); 558 p != null; 559 c = p, p = p.getParentNode()) 560 { 561 if (p == endPointB) { 562 int index = indexOf(c, endPointB); 563 if (index < offsetB) return 1; 564 return -1; 565 } 566 } 567 568 // case 4: preorder traversal of context tree. 569 // Instead of literally walking the context tree in pre-order, 570 // we use relative node depth walking which is usually faster 571 572 int depthDiff = 0; 573 for ( Node n = endPointA; n != null; n = n.getParentNode() ) 574 depthDiff++; 575 for ( Node n = endPointB; n != null; n = n.getParentNode() ) 576 depthDiff--; 577 while (depthDiff > 0) { 578 endPointA = endPointA.getParentNode(); 579 depthDiff--; 580 } 581 while (depthDiff < 0) { 582 endPointB = endPointB.getParentNode(); 583 depthDiff++; 584 } 585 for (Node pA = endPointA.getParentNode(), 586 pB = endPointB.getParentNode(); 587 pA != pB; 588 pA = pA.getParentNode(), pB = pB.getParentNode() ) 589 { 590 endPointA = pA; 591 endPointB = pB; 592 } 593 for ( Node n = endPointA.getNextSibling(); 594 n != null; 595 n = n.getNextSibling() ) 596 { 597 if (n == endPointB) { 598 return 1; 599 } 600 } 601 return -1; 602 } 603 604 public void deleteContents() 605 throws DOMException 606 { 607 traverseContents(DELETE_CONTENTS); 608 } 609 610 public DocumentFragment extractContents() 611 throws DOMException 612 { 613 return traverseContents(EXTRACT_CONTENTS); 614 } 615 616 public DocumentFragment cloneContents() 617 throws DOMException 618 { 619 return traverseContents(CLONE_CONTENTS); 620 } 621 622 public void insertNode(Node newNode) 623 throws DOMException, RangeException 624 { 625 if ( newNode == null ) return; //throw exception? 626 627 int type = newNode.getNodeType(); 628 629 if (fDocument.errorChecking) { 630 if (fDetach) { 631 throw new DOMException( 632 DOMException.INVALID_STATE_ERR, 633 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 634 } 635 if ( fDocument != newNode.getOwnerDocument() ) { 636 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, 637 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); 638 } 639 640 if (type == Node.ATTRIBUTE_NODE 641 || type == Node.ENTITY_NODE 642 || type == Node.NOTATION_NODE 643 || type == Node.DOCUMENT_NODE) 644 { 645 throw new RangeExceptionImpl( 646 RangeException.INVALID_NODE_TYPE_ERR, 647 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 648 } 649 } 650 Node cloneCurrent; 651 Node current; 652 int currentChildren = 0; 653 fInsertedFromRange = true; 654 655 //boolean MULTIPLE_MODE = false; 656 if (fStartContainer.getNodeType() == Node.TEXT_NODE) { 657 658 Node parent = fStartContainer.getParentNode(); 659 currentChildren = parent.getChildNodes().getLength(); //holds number of kids before insertion 660 // split text node: results is 3 nodes.. 661 cloneCurrent = fStartContainer.cloneNode(false); 662 ((TextImpl)cloneCurrent).setNodeValueInternal( 663 (cloneCurrent.getNodeValue()).substring(fStartOffset)); 664 ((TextImpl)fStartContainer).setNodeValueInternal( 665 (fStartContainer.getNodeValue()).substring(0,fStartOffset)); 666 Node next = fStartContainer.getNextSibling(); 667 if (next != null) { 668 if (parent != null) { 669 parent.insertBefore(newNode, next); 670 parent.insertBefore(cloneCurrent, next); 671 } 672 } else { 673 if (parent != null) { 674 parent.appendChild(newNode); 675 parent.appendChild(cloneCurrent); 676 } 677 } 678 //update ranges after the insertion 679 if ( fEndContainer == fStartContainer) { 680 fEndContainer = cloneCurrent; //endContainer is the new Node created 681 fEndOffset -= fStartOffset; 682 } 683 else if ( fEndContainer == parent ) { //endContainer was not a text Node. 684 //endOffset + = number_of_children_added 685 fEndOffset += (parent.getChildNodes().getLength() - currentChildren); 686 } 687 688 // signal other Ranges to update their start/end containers/offsets 689 signalSplitData(fStartContainer, cloneCurrent, fStartOffset); 690 691 692 } else { // ! TEXT_NODE 693 if ( fEndContainer == fStartContainer ) //need to remember number of kids 694 currentChildren= fEndContainer.getChildNodes().getLength(); 695 696 current = fStartContainer.getFirstChild(); 697 int i = 0; 698 for(i = 0; i < fStartOffset && current != null; i++) { 699 current=current.getNextSibling(); 700 } 701 if (current != null) { 702 fStartContainer.insertBefore(newNode, current); 703 } else { 704 fStartContainer.appendChild(newNode); 705 } 706 //update fEndOffset. ex:<body><p/></body>. Range(start;end): body,0; body,1 707 // insert <h1>: <body></h1><p/></body>. Range(start;end): body,0; body,2 708 if ( fEndContainer == fStartContainer && fEndOffset != 0 ) { //update fEndOffset if not 0 709 fEndOffset += (fEndContainer.getChildNodes().getLength() - currentChildren); 710 } 711 } 712 fInsertedFromRange = false; 713 } 714 715 public void surroundContents(Node newParent) 716 throws DOMException, RangeException 717 { 718 if (newParent==null) return; 719 int type = newParent.getNodeType(); 720 721 if (fDocument.errorChecking) { 722 if (fDetach) { 723 throw new DOMException( 724 DOMException.INVALID_STATE_ERR, 725 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 726 } 727 if (type == Node.ATTRIBUTE_NODE 728 || type == Node.ENTITY_NODE 729 || type == Node.NOTATION_NODE 730 || type == Node.DOCUMENT_TYPE_NODE 731 || type == Node.DOCUMENT_NODE 732 || type == Node.DOCUMENT_FRAGMENT_NODE) 733 { 734 throw new RangeExceptionImpl( 735 RangeException.INVALID_NODE_TYPE_ERR, 736 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); 737 } 738 } 739 740 Node realStart = fStartContainer; 741 Node realEnd = fEndContainer; 742 if (fStartContainer.getNodeType() == Node.TEXT_NODE) { 743 realStart = fStartContainer.getParentNode(); 744 } 745 if (fEndContainer.getNodeType() == Node.TEXT_NODE) { 746 realEnd = fEndContainer.getParentNode(); 747 } 748 749 if (realStart != realEnd) { 750 throw new RangeExceptionImpl( 751 RangeException.BAD_BOUNDARYPOINTS_ERR, 752 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "BAD_BOUNDARYPOINTS_ERR", null)); 753 } 754 755 DocumentFragment frag = extractContents(); 756 insertNode(newParent); 757 newParent.appendChild(frag); 758 selectNode(newParent); 759 } 760 761 public Range cloneRange(){ 762 if( fDetach) { 763 throw new DOMException( 764 DOMException.INVALID_STATE_ERR, 765 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 766 } 767 768 Range range = fDocument.createRange(); 769 range.setStart(fStartContainer, fStartOffset); 770 range.setEnd(fEndContainer, fEndOffset); 771 return range; 772 } 773 774 public String toString(){ 775 if( fDetach) { 776 throw new DOMException( 777 DOMException.INVALID_STATE_ERR, 778 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 779 } 780 781 Node node = fStartContainer; 782 Node stopNode = fEndContainer; 783 StringBuffer sb = new StringBuffer(); 784 if (fStartContainer.getNodeType() == Node.TEXT_NODE 785 || fStartContainer.getNodeType() == Node.CDATA_SECTION_NODE 786 ) { 787 if (fStartContainer == fEndContainer) { 788 sb.append(fStartContainer.getNodeValue().substring(fStartOffset, fEndOffset)); 789 return sb.toString(); 790 } 791 sb.append(fStartContainer.getNodeValue().substring(fStartOffset)); 792 node=nextNode (node,true); //fEndContainer!=fStartContainer 793 794 } 795 else { //fStartContainer is not a TextNode 796 node=node.getFirstChild(); 797 if (fStartOffset>0) { //find a first node within a range, specified by fStartOffset 798 int counter=0; 799 while (counter<fStartOffset && node!=null) { 800 node=node.getNextSibling(); 801 counter++; 802 } 803 } 804 if (node == null) { 805 node = nextNode(fStartContainer,false); 806 } 807 } 808 if ( fEndContainer.getNodeType()!= Node.TEXT_NODE && 809 fEndContainer.getNodeType()!= Node.CDATA_SECTION_NODE ){ 810 int i=fEndOffset; 811 stopNode = fEndContainer.getFirstChild(); 812 while( i>0 && stopNode!=null ){ 813 --i; 814 stopNode = stopNode.getNextSibling(); 815 } 816 if ( stopNode == null ) 817 stopNode = nextNode( fEndContainer, false ); 818 } 819 while (node != stopNode) { //look into all kids of the Range 820 if (node == null) break; 821 if (node.getNodeType() == Node.TEXT_NODE 822 || node.getNodeType() == Node.CDATA_SECTION_NODE) { 823 sb.append(node.getNodeValue()); 824 } 825 826 node = nextNode(node, true); 827 } 828 829 if (fEndContainer.getNodeType() == Node.TEXT_NODE 830 || fEndContainer.getNodeType() == Node.CDATA_SECTION_NODE) { 831 sb.append(fEndContainer.getNodeValue().substring(0,fEndOffset)); 832 } 833 return sb.toString(); 834 } 835 836 public void detach() { 837 if( fDetach) { 838 throw new DOMException( 839 DOMException.INVALID_STATE_ERR, 840 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 841 } 842 fDetach = true; 843 fDocument.removeRange(this); 844 } 845 846 // 847 // Mutation functions 848 // 849 850 /** Signal other Ranges to update their start/end 851 * containers/offsets. The data has already been split 852 * into the two Nodes. 853 */ 854 void signalSplitData(Node node, Node newNode, int offset) { 855 fSplitNode = node; 856 // notify document 857 fDocument.splitData(node, newNode, offset); 858 fSplitNode = null; 859 } 860 861 /** Fix up this Range if another Range has split a Text Node 862 * into 2 Nodes. 863 */ 864 void receiveSplitData(Node node, Node newNode, int offset) { 865 if (node == null || newNode == null) return; 866 if (fSplitNode == node) return; 867 868 if (node == fStartContainer 869 && fStartContainer.getNodeType() == Node.TEXT_NODE) { 870 if (fStartOffset > offset) { 871 fStartOffset = fStartOffset - offset; 872 fStartContainer = newNode; 873 } 874 } 875 if (node == fEndContainer 876 && fEndContainer.getNodeType() == Node.TEXT_NODE) { 877 if (fEndOffset > offset) { 878 fEndOffset = fEndOffset-offset; 879 fEndContainer = newNode; 880 } 881 } 882 883 } 884 885 /** This function inserts text into a Node and invokes 886 * a method to fix-up all other Ranges. 887 */ 888 void deleteData(CharacterData node, int offset, int count) { 889 fDeleteNode = node; 890 node.deleteData( offset, count); 891 fDeleteNode = null; 892 } 893 894 895 /** This function is called from DOM. 896 * The text has already beeen inserted. 897 * Fix-up any offsets. 898 */ 899 void receiveDeletedText(Node node, int offset, int count) { 900 if (node == null) return; 901 if (fDeleteNode == node) return; 902 if (node == fStartContainer 903 && fStartContainer.getNodeType() == Node.TEXT_NODE) { 904 if (fStartOffset > offset+count) { 905 fStartOffset = offset+(fStartOffset-(offset+count)); 906 } else 907 if (fStartOffset > offset) { 908 fStartOffset = offset; 909 } 910 } 911 if (node == fEndContainer 912 && fEndContainer.getNodeType() == Node.TEXT_NODE) { 913 if (fEndOffset > offset+count) { 914 fEndOffset = offset+(fEndOffset-(offset+count)); 915 } else 916 if (fEndOffset > offset) { 917 fEndOffset = offset; 918 } 919 } 920 921 } 922 923 /** This function inserts text into a Node and invokes 924 * a method to fix-up all other Ranges. 925 */ 926 void insertData(CharacterData node, int index, String insert) { 927 fInsertNode = node; 928 node.insertData( index, insert); 929 fInsertNode = null; 930 } 931 932 933 /** This function is called from DOM. 934 * The text has already beeen inserted. 935 * Fix-up any offsets. 936 */ 937 void receiveInsertedText(Node node, int index, int len) { 938 if (node == null) return; 939 if (fInsertNode == node) return; 940 if (node == fStartContainer 941 && fStartContainer.getNodeType() == Node.TEXT_NODE) { 942 if (index < fStartOffset) { 943 fStartOffset = fStartOffset+len; 944 } 945 } 946 if (node == fEndContainer 947 && fEndContainer.getNodeType() == Node.TEXT_NODE) { 948 if (index < fEndOffset) { 949 fEndOffset = fEndOffset+len; 950 } 951 } 952 953 } 954 955 /** This function is called from DOM. 956 * The text has already beeen replaced. 957 * Fix-up any offsets. 958 */ 959 void receiveReplacedText(Node node) { 960 if (node == null) return; 961 if (node == fStartContainer 962 && fStartContainer.getNodeType() == Node.TEXT_NODE) { 963 fStartOffset = 0; 964 } 965 if (node == fEndContainer 966 && fEndContainer.getNodeType() == Node.TEXT_NODE) { 967 fEndOffset = 0; 968 } 969 970 } 971 972 /** This function is called from the DOM. 973 * This node has already been inserted into the DOM. 974 * Fix-up any offsets. 975 */ 976 public void insertedNodeFromDOM(Node node) { 977 if (node == null) return; 978 if (fInsertNode == node) return; 979 if (fInsertedFromRange) return; // Offsets are adjusted in Range.insertNode 980 981 Node parent = node.getParentNode(); 982 983 if (parent == fStartContainer) { 984 int index = indexOf(node, fStartContainer); 985 if (index < fStartOffset) { 986 fStartOffset++; 987 } 988 } 989 990 if (parent == fEndContainer) { 991 int index = indexOf(node, fEndContainer); 992 if (index < fEndOffset) { 993 fEndOffset++; 994 } 995 } 996 997 } 998 999 /** This function is called within Range 1000 * instead of Node.removeChild, 1001 * so that the range can remember that it is actively 1002 * removing this child. 1003 */ 1004 1005 Node fRemoveChild = null; 1006 Node removeChild(Node parent, Node child) { 1007 fRemoveChild = child; 1008 Node n = parent.removeChild(child); 1009 fRemoveChild = null; 1010 return n; 1011 } 1012 1013 /** This function must be called by the DOM _BEFORE_ 1014 * a node is deleted, because at that time it is 1015 * connected in the DOM tree, which we depend on. 1016 */ 1017 void removeNode(Node node) { 1018 if (node == null) return; 1019 if (fRemoveChild == node) return; 1020 1021 Node parent = node.getParentNode(); 1022 1023 if (parent == fStartContainer) { 1024 int index = indexOf(node, fStartContainer); 1025 if (index < fStartOffset) { 1026 fStartOffset--; 1027 } 1028 } 1029 1030 if (parent == fEndContainer) { 1031 int index = indexOf(node, fEndContainer); 1032 if (index < fEndOffset) { 1033 fEndOffset--; 1034 } 1035 } 1036 //startContainer or endContainer or both is/are the ancestor(s) of the Node to be deleted 1037 if (parent != fStartContainer 1038 || parent != fEndContainer) { 1039 if (isAncestorOf(node, fStartContainer)) { 1040 fStartContainer = parent; 1041 fStartOffset = indexOf( node, parent); 1042 } 1043 if (isAncestorOf(node, fEndContainer)) { 1044 fEndContainer = parent; 1045 fEndOffset = indexOf( node, parent); 1046 } 1047 } 1048 1049 } 1050 1051 // 1052 // Utility functions. 1053 // 1054 1055 // parameters for traverseContents(int) 1056 //REVIST: use boolean, since there are only 2 now... 1057 static final int EXTRACT_CONTENTS = 1; 1058 static final int CLONE_CONTENTS = 2; 1059 static final int DELETE_CONTENTS = 3; 1060 1061 /** 1062 * This is the master routine invoked to visit the nodes 1063 * selected by this range. For each such node, different 1064 * actions are taken depending on the value of the 1065 * <code>how</code> argument. 1066 * 1067 * @param how Specifies what type of traversal is being 1068 * requested (extract, clone, or delete). 1069 * Legal values for this argument are: 1070 * 1071 * <ol> 1072 * <li><code>EXTRACT_CONTENTS</code> - will produce 1073 * a document fragment containing the range's content. 1074 * Partially selected nodes are copied, but fully 1075 * selected nodes are moved. 1076 * 1077 * <li><code>CLONE_CONTENTS</code> - will leave the 1078 * context tree of the range undisturbed, but sill 1079 * produced cloned content in a document fragment 1080 * 1081 * <li><code>DELETE_CONTENTS</code> - will delete from 1082 * the context tree of the range, all fully selected 1083 * nodes. 1084 * </ol> 1085 * 1086 * @return Returns a document fragment containing any 1087 * copied or extracted nodes. If the <code>how</code> 1088 * parameter was <code>DELETE_CONTENTS</code>, the 1089 * return value is null. 1090 */ 1091 private DocumentFragment traverseContents( int how ) 1092 throws DOMException 1093 { 1094 if (fStartContainer == null || fEndContainer == null) { 1095 return null; // REVIST: Throw exception? 1096 } 1097 1098 //Check for a detached range. 1099 if( fDetach) { 1100 throw new DOMException( 1101 DOMException.INVALID_STATE_ERR, 1102 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); 1103 } 1104 1105 /* 1106 Traversal is accomplished by first determining the 1107 relationship between the endpoints of the range. 1108 For each of four significant relationships, we will 1109 delegate the traversal call to a method that 1110 can make appropriate assumptions. 1111 */ 1112 1113 // case 1: same container 1114 if ( fStartContainer == fEndContainer ) 1115 return traverseSameContainer( how ); 1116 1117 1118 // case 2: Child C of start container is ancestor of end container 1119 // This can be quickly tested by walking the parent chain of 1120 // end container 1121 int endContainerDepth = 0; 1122 for ( Node c = fEndContainer, p = c.getParentNode(); 1123 p != null; 1124 c = p, p = p.getParentNode()) 1125 { 1126 if (p == fStartContainer) 1127 return traverseCommonStartContainer( c, how ); 1128 ++endContainerDepth; 1129 } 1130 1131 // case 3: Child C of container B is ancestor of A 1132 // This can be quickly tested by walking the parent chain of A 1133 int startContainerDepth = 0; 1134 for ( Node c = fStartContainer, p = c.getParentNode(); 1135 p != null; 1136 c = p, p = p.getParentNode()) 1137 { 1138 if (p == fEndContainer) 1139 return traverseCommonEndContainer( c, how ); 1140 ++startContainerDepth; 1141 } 1142 1143 // case 4: There is a common ancestor container. Find the 1144 // ancestor siblings that are children of that container. 1145 int depthDiff = startContainerDepth - endContainerDepth; 1146 1147 Node startNode = fStartContainer; 1148 while (depthDiff > 0) { 1149 startNode = startNode.getParentNode(); 1150 depthDiff--; 1151 } 1152 1153 Node endNode = fEndContainer; 1154 while (depthDiff < 0) { 1155 endNode = endNode.getParentNode(); 1156 depthDiff++; 1157 } 1158 1159 // ascend the ancestor hierarchy until we have a common parent. 1160 for( Node sp = startNode.getParentNode(), ep = endNode.getParentNode(); 1161 sp!=ep; 1162 sp = sp.getParentNode(), ep = ep.getParentNode() ) 1163 { 1164 startNode = sp; 1165 endNode = ep; 1166 } 1167 return traverseCommonAncestors( startNode, endNode, how ); 1168 } 1169 1170 /** 1171 * Visits the nodes selected by this range when we know 1172 * a-priori that the start and end containers are the same. 1173 * This method is invoked by the generic <code>traverse</code> 1174 * method. 1175 * 1176 * @param how Specifies what type of traversal is being 1177 * requested (extract, clone, or delete). 1178 * Legal values for this argument are: 1179 * 1180 * <ol> 1181 * <li><code>EXTRACT_CONTENTS</code> - will produce 1182 * a document fragment containing the range's content. 1183 * Partially selected nodes are copied, but fully 1184 * selected nodes are moved. 1185 * 1186 * <li><code>CLONE_CONTENTS</code> - will leave the 1187 * context tree of the range undisturbed, but sill 1188 * produced cloned content in a document fragment 1189 * 1190 * <li><code>DELETE_CONTENTS</code> - will delete from 1191 * the context tree of the range, all fully selected 1192 * nodes. 1193 * </ol> 1194 * 1195 * @return Returns a document fragment containing any 1196 * copied or extracted nodes. If the <code>how</code> 1197 * parameter was <code>DELETE_CONTENTS</code>, the 1198 * return value is null. 1199 */ 1200 private DocumentFragment traverseSameContainer( int how ) 1201 { 1202 DocumentFragment frag = null; 1203 if ( how!=DELETE_CONTENTS) 1204 frag = fDocument.createDocumentFragment(); 1205 1206 // If selection is empty, just return the fragment 1207 if ( fStartOffset==fEndOffset ) 1208 return frag; 1209 1210 // Text node needs special case handling 1211 if ( fStartContainer.getNodeType()==Node.TEXT_NODE ) 1212 { 1213 // get the substring 1214 String s = fStartContainer.getNodeValue(); 1215 String sub = s.substring( fStartOffset, fEndOffset ); 1216 1217 // set the original text node to its new value 1218 if ( how != CLONE_CONTENTS ) 1219 { 1220 ((TextImpl)fStartContainer).deleteData(fStartOffset, 1221 fEndOffset-fStartOffset) ; 1222 // Nothing is partially selected, so collapse to start point 1223 collapse( true ); 1224 } 1225 if ( how==DELETE_CONTENTS) 1226 return null; 1227 frag.appendChild( fDocument.createTextNode(sub) ); 1228 return frag; 1229 } 1230 1231 // Copy nodes between the start/end offsets. 1232 Node n = getSelectedNode( fStartContainer, fStartOffset ); 1233 int cnt = fEndOffset - fStartOffset; 1234 while( cnt > 0 ) 1235 { 1236 Node sibling = n.getNextSibling(); 1237 Node xferNode = traverseFullySelected( n, how ); 1238 if ( frag!=null ) 1239 frag.appendChild( xferNode ); 1240 --cnt; 1241 n = sibling; 1242 } 1243 1244 // Nothing is partially selected, so collapse to start point 1245 if ( how != CLONE_CONTENTS ) 1246 collapse( true ); 1247 return frag; 1248 } 1249 1250 /** 1251 * Visits the nodes selected by this range when we know 1252 * a-priori that the start and end containers are not the 1253 * same, but the start container is an ancestor of the 1254 * end container. This method is invoked by the generic 1255 * <code>traverse</code> method. 1256 * 1257 * @param endAncestor 1258 * The ancestor of the end container that is a direct child 1259 * of the start container. 1260 * 1261 * @param how Specifies what type of traversal is being 1262 * requested (extract, clone, or delete). 1263 * Legal values for this argument are: 1264 * 1265 * <ol> 1266 * <li><code>EXTRACT_CONTENTS</code> - will produce 1267 * a document fragment containing the range's content. 1268 * Partially selected nodes are copied, but fully 1269 * selected nodes are moved. 1270 * 1271 * <li><code>CLONE_CONTENTS</code> - will leave the 1272 * context tree of the range undisturbed, but sill 1273 * produced cloned content in a document fragment 1274 * 1275 * <li><code>DELETE_CONTENTS</code> - will delete from 1276 * the context tree of the range, all fully selected 1277 * nodes. 1278 * </ol> 1279 * 1280 * @return Returns a document fragment containing any 1281 * copied or extracted nodes. If the <code>how</code> 1282 * parameter was <code>DELETE_CONTENTS</code>, the 1283 * return value is null. 1284 */ 1285 private DocumentFragment 1286 traverseCommonStartContainer( Node endAncestor, int how ) 1287 { 1288 DocumentFragment frag = null; 1289 if ( how!=DELETE_CONTENTS) 1290 frag = fDocument.createDocumentFragment(); 1291 Node n = traverseRightBoundary( endAncestor, how ); 1292 if ( frag!=null ) 1293 frag.appendChild( n ); 1294 1295 int endIdx = indexOf( endAncestor, fStartContainer ); 1296 int cnt = endIdx - fStartOffset; 1297 if ( cnt <=0 ) 1298 { 1299 // Collapse to just before the endAncestor, which 1300 // is partially selected. 1301 if ( how != CLONE_CONTENTS ) 1302 { 1303 setEndBefore( endAncestor ); 1304 collapse( false ); 1305 } 1306 return frag; 1307 } 1308 1309 n = endAncestor.getPreviousSibling(); 1310 while( cnt > 0 ) 1311 { 1312 Node sibling = n.getPreviousSibling(); 1313 Node xferNode = traverseFullySelected( n, how ); 1314 if ( frag!=null ) 1315 frag.insertBefore( xferNode, frag.getFirstChild() ); 1316 --cnt; 1317 n = sibling; 1318 } 1319 // Collapse to just before the endAncestor, which 1320 // is partially selected. 1321 if ( how != CLONE_CONTENTS ) 1322 { 1323 setEndBefore( endAncestor ); 1324 collapse( false ); 1325 } 1326 return frag; 1327 } 1328 1329 /** 1330 * Visits the nodes selected by this range when we know 1331 * a-priori that the start and end containers are not the 1332 * same, but the end container is an ancestor of the 1333 * start container. This method is invoked by the generic 1334 * <code>traverse</code> method. 1335 * 1336 * @param startAncestor 1337 * The ancestor of the start container that is a direct 1338 * child of the end container. 1339 * 1340 * @param how Specifies what type of traversal is being 1341 * requested (extract, clone, or delete). 1342 * Legal values for this argument are: 1343 * 1344 * <ol> 1345 * <li><code>EXTRACT_CONTENTS</code> - will produce 1346 * a document fragment containing the range's content. 1347 * Partially selected nodes are copied, but fully 1348 * selected nodes are moved. 1349 * 1350 * <li><code>CLONE_CONTENTS</code> - will leave the 1351 * context tree of the range undisturbed, but sill 1352 * produced cloned content in a document fragment 1353 * 1354 * <li><code>DELETE_CONTENTS</code> - will delete from 1355 * the context tree of the range, all fully selected 1356 * nodes. 1357 * </ol> 1358 * 1359 * @return Returns a document fragment containing any 1360 * copied or extracted nodes. If the <code>how</code> 1361 * parameter was <code>DELETE_CONTENTS</code>, the 1362 * return value is null. 1363 */ 1364 private DocumentFragment 1365 traverseCommonEndContainer( Node startAncestor, int how ) 1366 { 1367 DocumentFragment frag = null; 1368 if ( how!=DELETE_CONTENTS) 1369 frag = fDocument.createDocumentFragment(); 1370 Node n = traverseLeftBoundary( startAncestor, how ); 1371 if ( frag!=null ) 1372 frag.appendChild( n ); 1373 int startIdx = indexOf( startAncestor, fEndContainer ); 1374 ++startIdx; // Because we already traversed it.... 1375 1376 int cnt = fEndOffset - startIdx; 1377 n = startAncestor.getNextSibling(); 1378 while( cnt > 0 ) 1379 { 1380 Node sibling = n.getNextSibling(); 1381 Node xferNode = traverseFullySelected( n, how ); 1382 if ( frag!=null ) 1383 frag.appendChild( xferNode ); 1384 --cnt; 1385 n = sibling; 1386 } 1387 1388 if ( how != CLONE_CONTENTS ) 1389 { 1390 setStartAfter( startAncestor ); 1391 collapse( true ); 1392 } 1393 1394 return frag; 1395 } 1396 1397 /** 1398 * Visits the nodes selected by this range when we know 1399 * a-priori that the start and end containers are not 1400 * the same, and we also know that neither the start 1401 * nor end container is an ancestor of the other. 1402 * This method is invoked by 1403 * the generic <code>traverse</code> method. 1404 * 1405 * @param startAncestor 1406 * Given a common ancestor of the start and end containers, 1407 * this parameter is the ancestor (or self) of the start 1408 * container that is a direct child of the common ancestor. 1409 * 1410 * @param endAncestor 1411 * Given a common ancestor of the start and end containers, 1412 * this parameter is the ancestor (or self) of the end 1413 * container that is a direct child of the common ancestor. 1414 * 1415 * @param how Specifies what type of traversal is being 1416 * requested (extract, clone, or delete). 1417 * Legal values for this argument are: 1418 * 1419 * <ol> 1420 * <li><code>EXTRACT_CONTENTS</code> - will produce 1421 * a document fragment containing the range's content. 1422 * Partially selected nodes are copied, but fully 1423 * selected nodes are moved. 1424 * 1425 * <li><code>CLONE_CONTENTS</code> - will leave the 1426 * context tree of the range undisturbed, but sill 1427 * produced cloned content in a document fragment 1428 * 1429 * <li><code>DELETE_CONTENTS</code> - will delete from 1430 * the context tree of the range, all fully selected 1431 * nodes. 1432 * </ol> 1433 * 1434 * @return Returns a document fragment containing any 1435 * copied or extracted nodes. If the <code>how</code> 1436 * parameter was <code>DELETE_CONTENTS</code>, the 1437 * return value is null. 1438 */ 1439 private DocumentFragment 1440 traverseCommonAncestors( Node startAncestor, Node endAncestor, int how ) 1441 { 1442 DocumentFragment frag = null; 1443 if ( how!=DELETE_CONTENTS) 1444 frag = fDocument.createDocumentFragment(); 1445 1446 Node n = traverseLeftBoundary( startAncestor, how ); 1447 if ( frag!=null ) 1448 frag.appendChild( n ); 1449 1450 Node commonParent = startAncestor.getParentNode(); 1451 int startOffset = indexOf( startAncestor, commonParent ); 1452 int endOffset = indexOf( endAncestor, commonParent ); 1453 ++startOffset; 1454 1455 int cnt = endOffset - startOffset; 1456 Node sibling = startAncestor.getNextSibling(); 1457 1458 while( cnt > 0 ) 1459 { 1460 Node nextSibling = sibling.getNextSibling(); 1461 n = traverseFullySelected( sibling, how ); 1462 if ( frag!=null ) 1463 frag.appendChild( n ); 1464 sibling = nextSibling; 1465 --cnt; 1466 } 1467 1468 n = traverseRightBoundary( endAncestor, how ); 1469 if ( frag!=null ) 1470 frag.appendChild( n ); 1471 1472 if ( how != CLONE_CONTENTS ) 1473 { 1474 setStartAfter( startAncestor ); 1475 collapse( true ); 1476 } 1477 return frag; 1478 } 1479 1480 /** 1481 * Traverses the "right boundary" of this range and 1482 * operates on each "boundary node" according to the 1483 * <code>how</code> parameter. It is a-priori assumed 1484 * by this method that the right boundary does 1485 * not contain the range's start container. 1486 * <p> 1487 * A "right boundary" is best visualized by thinking 1488 * of a sample tree:<pre> 1489 * A 1490 * /|\ 1491 * / | \ 1492 * / | \ 1493 * B C D 1494 * /|\ /|\ 1495 * E F G H I J 1496 * </pre> 1497 * Imagine first a range that begins between the 1498 * "E" and "F" nodes and ends between the 1499 * "I" and "J" nodes. The start container is 1500 * "B" and the end container is "D". Given this setup, 1501 * the following applies: 1502 * <p> 1503 * Partially Selected Nodes: B, D<br> 1504 * Fully Selected Nodes: F, G, C, H, I 1505 * <p> 1506 * The "right boundary" is the highest subtree node 1507 * that contains the ending container. The root of 1508 * this subtree is always partially selected. 1509 * <p> 1510 * In this example, the nodes that are traversed 1511 * as "right boundary" nodes are: H, I, and D. 1512 * 1513 * @param root The node that is the root of the "right boundary" subtree. 1514 * 1515 * @param how Specifies what type of traversal is being 1516 * requested (extract, clone, or delete). 1517 * Legal values for this argument are: 1518 * 1519 * <ol> 1520 * <li><code>EXTRACT_CONTENTS</code> - will produce 1521 * a node containing the boundaries content. 1522 * Partially selected nodes are copied, but fully 1523 * selected nodes are moved. 1524 * 1525 * <li><code>CLONE_CONTENTS</code> - will leave the 1526 * context tree of the range undisturbed, but will 1527 * produced cloned content. 1528 * 1529 * <li><code>DELETE_CONTENTS</code> - will delete from 1530 * the context tree of the range, all fully selected 1531 * nodes within the boundary. 1532 * </ol> 1533 * 1534 * @return Returns a node that is the result of visiting nodes. 1535 * If the traversal operation is 1536 * <code>DELETE_CONTENTS</code> the return value is null. 1537 */ 1538 private Node traverseRightBoundary( Node root, int how ) 1539 { 1540 Node next = getSelectedNode( fEndContainer, fEndOffset-1 ); 1541 boolean isFullySelected = ( next!=fEndContainer ); 1542 1543 if ( next==root ) 1544 return traverseNode( next, isFullySelected, false, how ); 1545 1546 Node parent = next.getParentNode(); 1547 Node clonedParent = traverseNode( parent, false, false, how ); 1548 1549 while( parent!=null ) 1550 { 1551 while( next!=null ) 1552 { 1553 Node prevSibling = next.getPreviousSibling(); 1554 Node clonedChild = 1555 traverseNode( next, isFullySelected, false, how ); 1556 if ( how!=DELETE_CONTENTS ) 1557 { 1558 clonedParent.insertBefore( 1559 clonedChild, 1560 clonedParent.getFirstChild() 1561 ); 1562 } 1563 isFullySelected = true; 1564 next = prevSibling; 1565 } 1566 if ( parent==root ) 1567 return clonedParent; 1568 1569 next = parent.getPreviousSibling(); 1570 parent = parent.getParentNode(); 1571 Node clonedGrandParent = traverseNode( parent, false, false, how ); 1572 if ( how!=DELETE_CONTENTS ) 1573 clonedGrandParent.appendChild( clonedParent ); 1574 clonedParent = clonedGrandParent; 1575 1576 } 1577 1578 // should never occur 1579 return null; 1580 } 1581 1582 /** 1583 * Traverses the "left boundary" of this range and 1584 * operates on each "boundary node" according to the 1585 * <code>how</code> parameter. It is a-priori assumed 1586 * by this method that the left boundary does 1587 * not contain the range's end container. 1588 * <p> 1589 * A "left boundary" is best visualized by thinking 1590 * of a sample tree:<pre> 1591 * 1592 * A 1593 * /|\ 1594 * / | \ 1595 * / | \ 1596 * B C D 1597 * /|\ /|\ 1598 * E F G H I J 1599 * </pre> 1600 * Imagine first a range that begins between the 1601 * "E" and "F" nodes and ends between the 1602 * "I" and "J" nodes. The start container is 1603 * "B" and the end container is "D". Given this setup, 1604 * the following applies: 1605 * <p> 1606 * Partially Selected Nodes: B, D<br> 1607 * Fully Selected Nodes: F, G, C, H, I 1608 * <p> 1609 * The "left boundary" is the highest subtree node 1610 * that contains the starting container. The root of 1611 * this subtree is always partially selected. 1612 * <p> 1613 * In this example, the nodes that are traversed 1614 * as "left boundary" nodes are: F, G, and B. 1615 * 1616 * @param root The node that is the root of the "left boundary" subtree. 1617 * 1618 * @param how Specifies what type of traversal is being 1619 * requested (extract, clone, or delete). 1620 * Legal values for this argument are: 1621 * 1622 * <ol> 1623 * <li><code>EXTRACT_CONTENTS</code> - will produce 1624 * a node containing the boundaries content. 1625 * Partially selected nodes are copied, but fully 1626 * selected nodes are moved. 1627 * 1628 * <li><code>CLONE_CONTENTS</code> - will leave the 1629 * context tree of the range undisturbed, but will 1630 * produced cloned content. 1631 * 1632 * <li><code>DELETE_CONTENTS</code> - will delete from 1633 * the context tree of the range, all fully selected 1634 * nodes within the boundary. 1635 * </ol> 1636 * 1637 * @return Returns a node that is the result of visiting nodes. 1638 * If the traversal operation is 1639 * <code>DELETE_CONTENTS</code> the return value is null. 1640 */ 1641 private Node traverseLeftBoundary( Node root, int how ) 1642 { 1643 Node next = getSelectedNode( getStartContainer(), getStartOffset() ); 1644 boolean isFullySelected = ( next!=getStartContainer() ); 1645 1646 if ( next==root ) 1647 return traverseNode( next, isFullySelected, true, how ); 1648 1649 Node parent = next.getParentNode(); 1650 Node clonedParent = traverseNode( parent, false, true, how ); 1651 1652 while( parent!=null ) 1653 { 1654 while( next!=null ) 1655 { 1656 Node nextSibling = next.getNextSibling(); 1657 Node clonedChild = 1658 traverseNode( next, isFullySelected, true, how ); 1659 if ( how!=DELETE_CONTENTS ) 1660 clonedParent.appendChild(clonedChild); 1661 isFullySelected = true; 1662 next = nextSibling; 1663 } 1664 if ( parent==root ) 1665 return clonedParent; 1666 1667 next = parent.getNextSibling(); 1668 parent = parent.getParentNode(); 1669 Node clonedGrandParent = traverseNode( parent, false, true, how ); 1670 if ( how!=DELETE_CONTENTS ) 1671 clonedGrandParent.appendChild( clonedParent ); 1672 clonedParent = clonedGrandParent; 1673 1674 } 1675 1676 // should never occur 1677 return null; 1678 1679 } 1680 1681 /** 1682 * Utility method for traversing a single node. 1683 * Does not properly handle a text node containing both the 1684 * start and end offsets. Such nodes should 1685 * have been previously detected and been routed to traverseTextNode. 1686 * 1687 * @param n The node to be traversed. 1688 * 1689 * @param isFullySelected 1690 * Set to true if the node is fully selected. Should be 1691 * false otherwise. 1692 * Note that although the DOM 2 specification says that a 1693 * text node that is boththe start and end container is not 1694 * selected, we treat it here as if it were partially 1695 * selected. 1696 * 1697 * @param isLeft Is true if we are traversing the node as part of navigating 1698 * the "left boundary" of the range. If this value is false, 1699 * it implies we are navigating the "right boundary" of the 1700 * range. 1701 * 1702 * @param how Specifies what type of traversal is being 1703 * requested (extract, clone, or delete). 1704 * Legal values for this argument are: 1705 * 1706 * <ol> 1707 * <li><code>EXTRACT_CONTENTS</code> - will simply 1708 * return the original node. 1709 * 1710 * <li><code>CLONE_CONTENTS</code> - will leave the 1711 * context tree of the range undisturbed, but will 1712 * return a cloned node. 1713 * 1714 * <li><code>DELETE_CONTENTS</code> - will delete the 1715 * node from it's parent, but will return null. 1716 * </ol> 1717 * 1718 * @return Returns a node that is the result of visiting the node. 1719 * If the traversal operation is 1720 * <code>DELETE_CONTENTS</code> the return value is null. 1721 */ 1722 private Node traverseNode( Node n, boolean isFullySelected, boolean isLeft, int how ) 1723 { 1724 if ( isFullySelected ) 1725 return traverseFullySelected( n, how ); 1726 if ( n.getNodeType()==Node.TEXT_NODE ) 1727 return traverseTextNode( n, isLeft, how ); 1728 return traversePartiallySelected( n, how ); 1729 } 1730 1731 /** 1732 * Utility method for traversing a single node when 1733 * we know a-priori that the node if fully 1734 * selected. 1735 * 1736 * @param n The node to be traversed. 1737 * 1738 * @param how Specifies what type of traversal is being 1739 * requested (extract, clone, or delete). 1740 * Legal values for this argument are: 1741 * 1742 * <ol> 1743 * <li><code>EXTRACT_CONTENTS</code> - will simply 1744 * return the original node. 1745 * 1746 * <li><code>CLONE_CONTENTS</code> - will leave the 1747 * context tree of the range undisturbed, but will 1748 * return a cloned node. 1749 * 1750 * <li><code>DELETE_CONTENTS</code> - will delete the 1751 * node from it's parent, but will return null. 1752 * </ol> 1753 * 1754 * @return Returns a node that is the result of visiting the node. 1755 * If the traversal operation is 1756 * <code>DELETE_CONTENTS</code> the return value is null. 1757 */ 1758 private Node traverseFullySelected( Node n, int how ) 1759 { 1760 switch( how ) 1761 { 1762 case CLONE_CONTENTS: 1763 return n.cloneNode( true ); 1764 case EXTRACT_CONTENTS: 1765 if ( n.getNodeType()==Node.DOCUMENT_TYPE_NODE ) 1766 { 1767 // TBD: This should be a HIERARCHY_REQUEST_ERR 1768 throw new DOMException( 1769 DOMException.HIERARCHY_REQUEST_ERR, 1770 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null)); 1771 } 1772 return n; 1773 case DELETE_CONTENTS: 1774 n.getParentNode().removeChild(n); 1775 return null; 1776 } 1777 return null; 1778 } 1779 1780 /** 1781 * Utility method for traversing a single node when 1782 * we know a-priori that the node if partially 1783 * selected and is not a text node. 1784 * 1785 * @param n The node to be traversed. 1786 * 1787 * @param how Specifies what type of traversal is being 1788 * requested (extract, clone, or delete). 1789 * Legal values for this argument are: 1790 * 1791 * <ol> 1792 * <li><code>EXTRACT_CONTENTS</code> - will simply 1793 * return the original node. 1794 * 1795 * <li><code>CLONE_CONTENTS</code> - will leave the 1796 * context tree of the range undisturbed, but will 1797 * return a cloned node. 1798 * 1799 * <li><code>DELETE_CONTENTS</code> - will delete the 1800 * node from it's parent, but will return null. 1801 * </ol> 1802 * 1803 * @return Returns a node that is the result of visiting the node. 1804 * If the traversal operation is 1805 * <code>DELETE_CONTENTS</code> the return value is null. 1806 */ 1807 private Node traversePartiallySelected( Node n, int how ) 1808 { 1809 switch( how ) 1810 { 1811 case DELETE_CONTENTS: 1812 return null; 1813 case CLONE_CONTENTS: 1814 case EXTRACT_CONTENTS: 1815 return n.cloneNode( false ); 1816 } 1817 return null; 1818 } 1819 1820 /** 1821 * Utility method for traversing a text node that we know 1822 * a-priori to be on a left or right boundary of the range. 1823 * This method does not properly handle text nodes that contain 1824 * both the start and end points of the range. 1825 * 1826 * @param n The node to be traversed. 1827 * 1828 * @param isLeft Is true if we are traversing the node as part of navigating 1829 * the "left boundary" of the range. If this value is false, 1830 * it implies we are navigating the "right boundary" of the 1831 * range. 1832 * 1833 * @param how Specifies what type of traversal is being 1834 * requested (extract, clone, or delete). 1835 * Legal values for this argument are: 1836 * 1837 * <ol> 1838 * <li><code>EXTRACT_CONTENTS</code> - will simply 1839 * return the original node. 1840 * 1841 * <li><code>CLONE_CONTENTS</code> - will leave the 1842 * context tree of the range undisturbed, but will 1843 * return a cloned node. 1844 * 1845 * <li><code>DELETE_CONTENTS</code> - will delete the 1846 * node from it's parent, but will return null. 1847 * </ol> 1848 * 1849 * @return Returns a node that is the result of visiting the node. 1850 * If the traversal operation is 1851 * <code>DELETE_CONTENTS</code> the return value is null. 1852 */ 1853 private Node traverseTextNode( Node n, boolean isLeft, int how ) 1854 { 1855 String txtValue = n.getNodeValue(); 1856 String newNodeValue; 1857 String oldNodeValue; 1858 1859 if ( isLeft ) 1860 { 1861 int offset = getStartOffset(); 1862 newNodeValue = txtValue.substring( offset ); 1863 oldNodeValue = txtValue.substring( 0, offset ); 1864 } 1865 else 1866 { 1867 int offset = getEndOffset(); 1868 newNodeValue = txtValue.substring( 0, offset ); 1869 oldNodeValue = txtValue.substring( offset ); 1870 } 1871 1872 if ( how != CLONE_CONTENTS ) 1873 n.setNodeValue( oldNodeValue ); 1874 if ( how==DELETE_CONTENTS ) 1875 return null; 1876 Node newNode = n.cloneNode( false ); 1877 newNode.setNodeValue( newNodeValue ); 1878 return newNode; 1879 } 1880 1881 void checkIndex(Node refNode, int offset) throws DOMException 1882 { 1883 if (offset < 0) { 1884 throw new DOMException( 1885 DOMException.INDEX_SIZE_ERR, 1886 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null)); 1887 } 1888 1889 int type = refNode.getNodeType(); 1890 1891 // If the node contains text, ensure that the 1892 // offset of the range is <= to the length of the text 1893 if (type == Node.TEXT_NODE 1894 || type == Node.CDATA_SECTION_NODE 1895 || type == Node.COMMENT_NODE 1896 || type == Node.PROCESSING_INSTRUCTION_NODE) { 1897 if (offset > refNode.getNodeValue().length()) { 1898 throw new DOMException(DOMException.INDEX_SIZE_ERR, 1899 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null)); 1900 } 1901 } 1902 else { 1903 // Since the node is not text, ensure that the offset 1904 // is valid with respect to the number of child nodes 1905 if (offset > refNode.getChildNodes().getLength()) { 1906 throw new DOMException(DOMException.INDEX_SIZE_ERR, 1907 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null)); 1908 } 1909 } 1910 } 1911 1912 /** 1913 * Given a node, calculate what the Range's root container 1914 * for that node would be. 1915 */ 1916 private Node getRootContainer( Node node ) 1917 { 1918 if ( node==null ) 1919 return null; 1920 1921 while( node.getParentNode()!=null ) 1922 node = node.getParentNode(); 1923 return node; 1924 } 1925 1926 /** 1927 * Returns true IFF the given node can serve as a container 1928 * for a range's boundary points. 1929 */ 1930 private boolean isLegalContainer( Node node ) 1931 { 1932 if ( node==null ) 1933 return false; 1934 1935 while( node!=null ) 1936 { 1937 switch( node.getNodeType() ) 1938 { 1939 case Node.ENTITY_NODE: 1940 case Node.NOTATION_NODE: 1941 case Node.DOCUMENT_TYPE_NODE: 1942 return false; 1943 } 1944 node = node.getParentNode(); 1945 } 1946 1947 return true; 1948 } 1949 1950 1951 /** 1952 * Finds the root container for the given node and determines 1953 * if that root container is legal with respect to the 1954 * DOM 2 specification. At present, that means the root 1955 * container must be either an attribute, a document, 1956 * or a document fragment. 1957 */ 1958 private boolean hasLegalRootContainer( Node node ) 1959 { 1960 if ( node==null ) 1961 return false; 1962 1963 Node rootContainer = getRootContainer( node ); 1964 switch( rootContainer.getNodeType() ) 1965 { 1966 case Node.ATTRIBUTE_NODE: 1967 case Node.DOCUMENT_NODE: 1968 case Node.DOCUMENT_FRAGMENT_NODE: 1969 return true; 1970 } 1971 return false; 1972 } 1973 1974 /** 1975 * Returns true IFF the given node can be contained by 1976 * a range. 1977 */ 1978 private boolean isLegalContainedNode( Node node ) 1979 { 1980 if ( node==null ) 1981 return false; 1982 switch( node.getNodeType() ) 1983 { 1984 case Node.DOCUMENT_NODE: 1985 case Node.DOCUMENT_FRAGMENT_NODE: 1986 case Node.ATTRIBUTE_NODE: 1987 case Node.ENTITY_NODE: 1988 case Node.NOTATION_NODE: 1989 return false; 1990 } 1991 return true; 1992 } 1993 1994 Node nextNode(Node node, boolean visitChildren) { 1995 1996 if (node == null) return null; 1997 1998 Node result; 1999 if (visitChildren) { 2000 result = node.getFirstChild(); 2001 if (result != null) { 2002 return result; 2003 } 2004 } 2005 2006 // if hasSibling, return sibling 2007 result = node.getNextSibling(); 2008 if (result != null) { 2009 return result; 2010 } 2011 2012 2013 // return parent's 1st sibling. 2014 Node parent = node.getParentNode(); 2015 while (parent != null 2016 && parent != fDocument 2017 ) { 2018 result = parent.getNextSibling(); 2019 if (result != null) { 2020 return result; 2021 } else { 2022 parent = parent.getParentNode(); 2023 } 2024 2025 } // while (parent != null && parent != fRoot) { 2026 2027 // end of list, return null 2028 return null; 2029 } 2030 2031 /** is a an ancestor of b ? */ 2032 boolean isAncestorOf(Node a, Node b) { 2033 for (Node node=b; node != null; node=node.getParentNode()) { 2034 if (node == a) return true; 2035 } 2036 return false; 2037 } 2038 2039 /** what is the index of the child in the parent */ 2040 int indexOf(Node child, Node parent) { 2041 if (child.getParentNode() != parent) return -1; 2042 int i = 0; 2043 for(Node node = parent.getFirstChild(); node!= child; node=node.getNextSibling()) { 2044 i++; 2045 } 2046 return i; 2047 } 2048 2049 /** 2050 * Utility method to retrieve a child node by index. This method 2051 * assumes the caller is trying to find out which node is 2052 * selected by the given index. Note that if the index is 2053 * greater than the number of children, this implies that the 2054 * first node selected is the parent node itself. 2055 * 2056 * @param container A container node 2057 * 2058 * @param offset An offset within the container for which a selected node should 2059 * be computed. If the offset is less than zero, or if the offset 2060 * is greater than the number of children, the container is returned. 2061 * 2062 * @return Returns either a child node of the container or the 2063 * container itself. 2064 */ 2065 private Node getSelectedNode( Node container, int offset ) 2066 { 2067 if ( container.getNodeType() == Node.TEXT_NODE ) 2068 return container; 2069 2070 // This case is an important convenience for 2071 // traverseRightBoundary() 2072 if ( offset<0 ) 2073 return container; 2074 2075 Node child = container.getFirstChild(); 2076 while( child!=null && offset > 0 ) 2077 { 2078 --offset; 2079 child = child.getNextSibling(); 2080 } 2081 if ( child!=null ) 2082 return child; 2083 return container; 2084 } 2085 2086 }