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