[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/libraries/ckeditor/_source/core/dom/ -> range.js (source)

   1  /*

   2  Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.

   3  For licensing, see LICENSE.html or http://ckeditor.com/license

   4  */
   5  
   6  CKEDITOR.dom.range = function( document )
   7  {
   8      this.startContainer    = null;
   9      this.startOffset    = null;
  10      this.endContainer    = null;
  11      this.endOffset        = null;
  12      this.collapsed        = true;
  13  
  14      this.document = document;
  15  };
  16  
  17  (function()
  18  {
  19      // Updates the "collapsed" property for the given range object.

  20      var updateCollapsed = function( range )
  21      {
  22          range.collapsed = (
  23              range.startContainer &&
  24              range.endContainer &&
  25              range.startContainer.equals( range.endContainer ) &&
  26              range.startOffset == range.endOffset );
  27      };
  28  
  29      // This is a shared function used to delete, extract and clone the range

  30      // contents.

  31      // V2

  32      var execContentsAction = function( range, action, docFrag )
  33      {
  34          range.optimizeBookmark();
  35  
  36          var startNode    = range.startContainer;
  37          var endNode        = range.endContainer;
  38  
  39          var startOffset    = range.startOffset;
  40          var endOffset    = range.endOffset;
  41  
  42          var removeStartNode;
  43          var removeEndNode;
  44  
  45          // For text containers, we must simply split the node and point to the

  46          // second part. The removal will be handled by the rest of the code .

  47          if ( endNode.type == CKEDITOR.NODE_TEXT )
  48              endNode = endNode.split( endOffset );
  49          else
  50          {
  51              // If the end container has children and the offset is pointing

  52              // to a child, then we should start from it.

  53              if ( endNode.getChildCount() > 0 )
  54              {
  55                  // If the offset points after the last node.

  56                  if ( endOffset >= endNode.getChildCount() )
  57                  {
  58                      // Let's create a temporary node and mark it for removal.

  59                      endNode = endNode.append( range.document.createText( '' ) );
  60                      removeEndNode = true;
  61                  }
  62                  else
  63                      endNode = endNode.getChild( endOffset );
  64              }
  65          }
  66  
  67          // For text containers, we must simply split the node. The removal will

  68          // be handled by the rest of the code .

  69          if ( startNode.type == CKEDITOR.NODE_TEXT )
  70          {
  71              startNode.split( startOffset );
  72  
  73              // In cases the end node is the same as the start node, the above

  74              // splitting will also split the end, so me must move the end to

  75              // the second part of the split.

  76              if ( startNode.equals( endNode ) )
  77                  endNode = startNode.getNext();
  78          }
  79          else
  80          {
  81              // If the start container has children and the offset is pointing

  82              // to a child, then we should start from its previous sibling.

  83  
  84              // If the offset points to the first node, we don't have a

  85              // sibling, so let's use the first one, but mark it for removal.

  86              if ( !startOffset )
  87              {
  88                  // Let's create a temporary node and mark it for removal.

  89                  startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
  90                  removeStartNode = true;
  91              }
  92              else if ( startOffset >= startNode.getChildCount() )
  93              {
  94                  // Let's create a temporary node and mark it for removal.

  95                  startNode = startNode.append( range.document.createText( '' ) );
  96                  removeStartNode = true;
  97              }
  98              else
  99                  startNode = startNode.getChild( startOffset ).getPrevious();
 100          }
 101  
 102          // Get the parent nodes tree for the start and end boundaries.

 103          var startParents    = startNode.getParents();
 104          var endParents        = endNode.getParents();
 105  
 106          // Compare them, to find the top most siblings.

 107          var i, topStart, topEnd;
 108  
 109          for ( i = 0 ; i < startParents.length ; i++ )
 110          {
 111              topStart = startParents[ i ];
 112              topEnd = endParents[ i ];
 113  
 114              // The compared nodes will match until we find the top most

 115              // siblings (different nodes that have the same parent).

 116              // "i" will hold the index in the parents array for the top

 117              // most element.

 118              if ( !topStart.equals( topEnd ) )
 119                  break;
 120          }
 121  
 122          var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
 123  
 124          // Remove all successive sibling nodes for every node in the

 125          // startParents tree.

 126          for ( var j = i ; j < startParents.length ; j++ )
 127          {
 128              levelStartNode = startParents[j];
 129  
 130              // For Extract and Clone, we must clone this level.

 131              if ( clone && !levelStartNode.equals( startNode ) )        // action = 0 = Delete
 132                  levelClone = clone.append( levelStartNode.clone() );
 133  
 134              currentNode = levelStartNode.getNext();
 135  
 136              while ( currentNode )
 137              {
 138                  // Stop processing when the current node matches a node in the

 139                  // endParents tree or if it is the endNode.

 140                  if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
 141                      break;
 142  
 143                  // Cache the next sibling.

 144                  currentSibling = currentNode.getNext();
 145  
 146                  // If cloning, just clone it.

 147                  if ( action == 2 )    // 2 = Clone
 148                      clone.append( currentNode.clone( true ) );
 149                  else
 150                  {
 151                      // Both Delete and Extract will remove the node.

 152                      currentNode.remove();
 153  
 154                      // When Extracting, move the removed node to the docFrag.

 155                      if ( action == 1 )    // 1 = Extract
 156                          clone.append( currentNode );
 157                  }
 158  
 159                  currentNode = currentSibling;
 160              }
 161  
 162              if ( clone )
 163                  clone = levelClone;
 164          }
 165  
 166          clone = docFrag;
 167  
 168          // Remove all previous sibling nodes for every node in the

 169          // endParents tree.

 170          for ( var k = i ; k < endParents.length ; k++ )
 171          {
 172              levelStartNode = endParents[ k ];
 173  
 174              // For Extract and Clone, we must clone this level.

 175              if ( action > 0 && !levelStartNode.equals( endNode ) )        // action = 0 = Delete
 176                  levelClone = clone.append( levelStartNode.clone() );
 177  
 178              // The processing of siblings may have already been done by the parent.

 179              if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
 180              {
 181                  currentNode = levelStartNode.getPrevious();
 182  
 183                  while ( currentNode )
 184                  {
 185                      // Stop processing when the current node matches a node in the

 186                      // startParents tree or if it is the startNode.

 187                      if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
 188                          break;
 189  
 190                      // Cache the next sibling.

 191                      currentSibling = currentNode.getPrevious();
 192  
 193                      // If cloning, just clone it.

 194                      if ( action == 2 )    // 2 = Clone
 195                          clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
 196                      else
 197                      {
 198                          // Both Delete and Extract will remove the node.

 199                          currentNode.remove();
 200  
 201                          // When Extracting, mode the removed node to the docFrag.

 202                          if ( action == 1 )    // 1 = Extract
 203                              clone.$.insertBefore( currentNode.$, clone.$.firstChild );
 204                      }
 205  
 206                      currentNode = currentSibling;
 207                  }
 208              }
 209  
 210              if ( clone )
 211                  clone = levelClone;
 212          }
 213  
 214          if ( action == 2 )        // 2 = Clone.
 215          {
 216              // No changes in the DOM should be done, so fix the split text (if any).

 217  
 218              var startTextNode = range.startContainer;
 219              if ( startTextNode.type == CKEDITOR.NODE_TEXT )
 220              {
 221                  startTextNode.$.data += startTextNode.$.nextSibling.data;
 222                  startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
 223              }
 224  
 225              var endTextNode = range.endContainer;
 226              if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
 227              {
 228                  endTextNode.$.data += endTextNode.$.nextSibling.data;
 229                  endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
 230              }
 231          }
 232          else
 233          {
 234              // Collapse the range.

 235  
 236              // If a node has been partially selected, collapse the range between

 237              // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).

 238              if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
 239              {
 240                  var endIndex = topEnd.getIndex();
 241  
 242                  // If the start node is to be removed, we must correct the

 243                  // index to reflect the removal.

 244                  if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
 245                      endIndex--;
 246  
 247                  range.setStart( topEnd.getParent(), endIndex );
 248              }
 249  
 250              // Collapse it to the start.

 251              range.collapse( true );
 252          }
 253  
 254          // Cleanup any marked node.

 255          if ( removeStartNode )
 256              startNode.remove();
 257  
 258          if ( removeEndNode && endNode.$.parentNode )
 259              endNode.remove();
 260      };
 261  
 262      var inlineChildReqElements = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 };
 263  
 264      // Creates the appropriate node evaluator for the dom walker used inside

 265      // check(Start|End)OfBlock.

 266  	function getCheckStartEndBlockEvalFunction( isStart )
 267      {
 268          var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true );
 269          return function( node )
 270          {
 271              // First ignore bookmark nodes.

 272              if ( bookmarkEvaluator( node ) )
 273                  return true;
 274  
 275              if ( node.type == CKEDITOR.NODE_TEXT )
 276              {
 277                  // If there's any visible text, then we're not at the start.

 278                  if ( CKEDITOR.tools.trim( node.getText() ).length )
 279                      return false;
 280                  }
 281              else if ( node.type == CKEDITOR.NODE_ELEMENT )
 282              {
 283                  // If there are non-empty inline elements (e.g. <img />), then we're not

 284                  // at the start.

 285                  if ( !inlineChildReqElements[ node.getName() ] )
 286                  {
 287                      // If we're working at the end-of-block, forgive the first <br /> in non-IE

 288                      // browsers.

 289                      if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr )
 290                          hadBr = true;
 291                      else
 292                          return false;
 293                  }
 294              }
 295              return true;
 296          };
 297      }
 298  
 299      // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any

 300      // text node and non-empty elements unless it's being bookmark text.

 301  	function elementBoundaryEval( node )
 302      {
 303          // Reject any text node unless it's being bookmark

 304          // OR it's spaces. (#3883)

 305          return node.type != CKEDITOR.NODE_TEXT
 306                  && node.getName() in CKEDITOR.dtd.$removeEmpty
 307                  || !CKEDITOR.tools.trim( node.getText() )
 308                  || node.getParent().hasAttribute( '_fck_bookmark' );
 309      }
 310  
 311      var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),
 312          bookmarkEval = new CKEDITOR.dom.walker.bookmark();
 313  
 314  	function nonWhitespaceOrBookmarkEval( node )
 315      {
 316          // Whitespaces and bookmark nodes are to be ignored.

 317          return !whitespaceEval( node ) && !bookmarkEval( node );
 318      }
 319  
 320      CKEDITOR.dom.range.prototype =
 321      {
 322          clone : function()
 323          {
 324              var clone = new CKEDITOR.dom.range( this.document );
 325  
 326              clone.startContainer = this.startContainer;
 327              clone.startOffset = this.startOffset;
 328              clone.endContainer = this.endContainer;
 329              clone.endOffset = this.endOffset;
 330              clone.collapsed = this.collapsed;
 331  
 332              return clone;
 333          },
 334  
 335          collapse : function( toStart )
 336          {
 337              if ( toStart )
 338              {
 339                  this.endContainer    = this.startContainer;
 340                  this.endOffset        = this.startOffset;
 341              }
 342              else
 343              {
 344                  this.startContainer    = this.endContainer;
 345                  this.startOffset    = this.endOffset;
 346              }
 347  
 348              this.collapsed = true;
 349          },
 350  
 351          // The selection may be lost when cloning (due to the splitText() call).

 352          cloneContents : function()
 353          {
 354              var docFrag = new CKEDITOR.dom.documentFragment( this.document );
 355  
 356              if ( !this.collapsed )
 357                  execContentsAction( this, 2, docFrag );
 358  
 359              return docFrag;
 360          },
 361  
 362          deleteContents : function()
 363          {
 364              if ( this.collapsed )
 365                  return;
 366  
 367              execContentsAction( this, 0 );
 368          },
 369  
 370          extractContents : function()
 371          {
 372              var docFrag = new CKEDITOR.dom.documentFragment( this.document );
 373  
 374              if ( !this.collapsed )
 375                  execContentsAction( this, 1, docFrag );
 376  
 377              return docFrag;
 378          },
 379  
 380          /**

 381           * Creates a bookmark object, which can be later used to restore the

 382           * range by using the moveToBookmark function.

 383           * This is an "intrusive" way to create a bookmark. It includes <span> tags

 384           * in the range boundaries. The advantage of it is that it is possible to

 385           * handle DOM mutations when moving back to the bookmark.

 386           * Attention: the inclusion of nodes in the DOM is a design choice and

 387           * should not be changed as there are other points in the code that may be

 388           * using those nodes to perform operations. See GetBookmarkNode.

 389           * @param {Boolean} [serializable] Indicates that the bookmark nodes

 390           *        must contain ids, which can be used to restore the range even

 391           *        when these nodes suffer mutations (like a clonation or innerHTML

 392           *        change).

 393           * @returns {Object} And object representing a bookmark.

 394           */
 395          createBookmark : function( serializable )
 396          {
 397              var startNode, endNode;
 398              var baseId;
 399              var clone;
 400              var collapsed = this.collapsed;
 401  
 402              startNode = this.document.createElement( 'span' );
 403              startNode.setAttribute( '_fck_bookmark', 1 );
 404              startNode.setStyle( 'display', 'none' );
 405  
 406              // For IE, it must have something inside, otherwise it may be

 407              // removed during DOM operations.

 408              startNode.setHtml( '&nbsp;' );
 409  
 410              if ( serializable )
 411              {
 412                  baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
 413                  startNode.setAttribute( 'id', baseId + 'S' );
 414              }
 415  
 416              // If collapsed, the endNode will not be created.

 417              if ( !collapsed )
 418              {
 419                  endNode = startNode.clone();
 420                  endNode.setHtml( '&nbsp;' );
 421  
 422                  if ( serializable )
 423                      endNode.setAttribute( 'id', baseId + 'E' );
 424  
 425                  clone = this.clone();
 426                  clone.collapse();
 427                  clone.insertNode( endNode );
 428              }
 429  
 430              clone = this.clone();
 431              clone.collapse( true );
 432              clone.insertNode( startNode );
 433  
 434              // Update the range position.

 435              if ( endNode )
 436              {
 437                  this.setStartAfter( startNode );
 438                  this.setEndBefore( endNode );
 439              }
 440              else
 441                  this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
 442  
 443              return {
 444                  startNode : serializable ? baseId + 'S' : startNode,
 445                  endNode : serializable ? baseId + 'E' : endNode,
 446                  serializable : serializable,
 447                  collapsed : collapsed
 448              };
 449          },
 450  
 451          /**

 452           * Creates a "non intrusive" and "mutation sensible" bookmark. This

 453           * kind of bookmark should be used only when the DOM is supposed to

 454           * remain stable after its creation.

 455           * @param {Boolean} [normalized] Indicates that the bookmark must

 456           *        normalized. When normalized, the successive text nodes are

 457           *        considered a single node. To sucessful load a normalized

 458           *        bookmark, the DOM tree must be also normalized before calling

 459           *        moveToBookmark.

 460           * @returns {Object} An object representing the bookmark.

 461           */
 462          createBookmark2 : function( normalized )
 463          {
 464              var startContainer    = this.startContainer,
 465                  endContainer    = this.endContainer;
 466  
 467              var startOffset    = this.startOffset,
 468                  endOffset    = this.endOffset;
 469  
 470              var collapsed = this.collapsed;
 471  
 472              var child, previous;
 473  
 474              // If there is no range then get out of here.

 475              // It happens on initial load in Safari #962 and if the editor it's

 476              // hidden also in Firefox

 477              if ( !startContainer || !endContainer )
 478                  return { start : 0, end : 0 };
 479  
 480              if ( normalized )
 481              {
 482                  // Find out if the start is pointing to a text node that will

 483                  // be normalized.

 484                  if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
 485                  {
 486                      child = startContainer.getChild( startOffset );
 487  
 488                      // In this case, move the start information to that text

 489                      // node.

 490                      if ( child && child.type == CKEDITOR.NODE_TEXT
 491                              && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
 492                      {
 493                          startContainer = child;
 494                          startOffset = 0;
 495                      }
 496                  }
 497  
 498                  // Normalize the start.

 499                  while ( startContainer.type == CKEDITOR.NODE_TEXT
 500                          && ( previous = startContainer.getPrevious() )
 501                          && previous.type == CKEDITOR.NODE_TEXT )
 502                  {
 503                      startContainer = previous;
 504                      startOffset += previous.getLength();
 505                  }
 506  
 507                  // Process the end only if not normalized.

 508                  if ( !collapsed )
 509                  {
 510                      // Find out if the start is pointing to a text node that

 511                      // will be normalized.

 512                      if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
 513                      {
 514                          child = endContainer.getChild( endOffset );
 515  
 516                          // In this case, move the start information to that

 517                          // text node.

 518                          if ( child && child.type == CKEDITOR.NODE_TEXT
 519                                  && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
 520                          {
 521                              endContainer = child;
 522                              endOffset = 0;
 523                          }
 524                      }
 525  
 526                      // Normalize the end.

 527                      while ( endContainer.type == CKEDITOR.NODE_TEXT
 528                              && ( previous = endContainer.getPrevious() )
 529                              && previous.type == CKEDITOR.NODE_TEXT )
 530                      {
 531                          endContainer = previous;
 532                          endOffset += previous.getLength();
 533                      }
 534                  }
 535              }
 536  
 537              return {
 538                  start        : startContainer.getAddress( normalized ),
 539                  end            : collapsed ? null : endContainer.getAddress( normalized ),
 540                  startOffset    : startOffset,
 541                  endOffset    : endOffset,
 542                  normalized    : normalized,
 543                  collapsed    : collapsed,
 544                  is2            : true        // It's a createBookmark2 bookmark.
 545              };
 546          },
 547  
 548          moveToBookmark : function( bookmark )
 549          {
 550              if ( bookmark.is2 )        // Created with createBookmark2().
 551              {
 552                  // Get the start information.

 553                  var startContainer    = this.document.getByAddress( bookmark.start, bookmark.normalized ),
 554                      startOffset    = bookmark.startOffset;
 555  
 556                  // Get the end information.

 557                  var endContainer    = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
 558                      endOffset    = bookmark.endOffset;
 559  
 560                  // Set the start boundary.

 561                  this.setStart( startContainer, startOffset );
 562  
 563                  // Set the end boundary. If not available, collapse it.

 564                  if ( endContainer )
 565                      this.setEnd( endContainer, endOffset );
 566                  else
 567                      this.collapse( true );
 568              }
 569              else                    // Created with createBookmark().
 570              {
 571                  var serializable = bookmark.serializable,
 572                      startNode    = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
 573                      endNode        = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
 574  
 575                  // Set the range start at the bookmark start node position.

 576                  this.setStartBefore( startNode );
 577  
 578                  // Remove it, because it may interfere in the setEndBefore call.

 579                  startNode.remove();
 580  
 581                  // Set the range end at the bookmark end node position, or simply

 582                  // collapse it if it is not available.

 583                  if ( endNode )
 584                  {
 585                      this.setEndBefore( endNode );
 586                      endNode.remove();
 587                  }
 588                  else
 589                      this.collapse( true );
 590              }
 591          },
 592  
 593          getBoundaryNodes : function()
 594          {
 595              var startNode = this.startContainer,
 596                  endNode = this.endContainer,
 597                  startOffset = this.startOffset,
 598                  endOffset = this.endOffset,
 599                  childCount;
 600  
 601              if ( startNode.type == CKEDITOR.NODE_ELEMENT )
 602              {
 603                  childCount = startNode.getChildCount();
 604                  if ( childCount > startOffset )
 605                      startNode = startNode.getChild( startOffset );
 606                  else if ( childCount < 1 )
 607                      startNode = startNode.getPreviousSourceNode();
 608                  else        // startOffset > childCount but childCount is not 0
 609                  {
 610                      // Try to take the node just after the current position.

 611                      startNode = startNode.$;
 612                      while ( startNode.lastChild )
 613                          startNode = startNode.lastChild;
 614                      startNode = new CKEDITOR.dom.node( startNode );
 615  
 616                      // Normally we should take the next node in DFS order. But it

 617                      // is also possible that we've already reached the end of

 618                      // document.

 619                      startNode = startNode.getNextSourceNode() || startNode;
 620                  }
 621              }
 622              if ( endNode.type == CKEDITOR.NODE_ELEMENT )
 623              {
 624                  childCount = endNode.getChildCount();
 625                  if ( childCount > endOffset )
 626                      endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
 627                  else if ( childCount < 1 )
 628                      endNode = endNode.getPreviousSourceNode();
 629                  else        // endOffset > childCount but childCount is not 0
 630                  {
 631                      // Try to take the node just before the current position.

 632                      endNode = endNode.$;
 633                      while ( endNode.lastChild )
 634                          endNode = endNode.lastChild;
 635                      endNode = new CKEDITOR.dom.node( endNode );
 636                  }
 637              }
 638  
 639              // Sometimes the endNode will come right before startNode for collapsed

 640              // ranges. Fix it. (#3780)

 641              if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
 642                  startNode = endNode;
 643  
 644              return { startNode : startNode, endNode : endNode };
 645          },
 646  
 647          /**

 648           * Find the node which fully contains the range.

 649           * @param includeSelf

 650           * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.

 651           */
 652          getCommonAncestor : function( includeSelf , ignoreTextNode )
 653          {
 654              var start = this.startContainer,
 655                  end = this.endContainer,
 656                  ancestor;
 657  
 658              if ( start.equals( end ) )
 659              {
 660                  if ( includeSelf
 661                          && start.type == CKEDITOR.NODE_ELEMENT
 662                          && this.startOffset == this.endOffset - 1 )
 663                      ancestor = start.getChild( this.startOffset );
 664                  else
 665                      ancestor = start;
 666              }
 667              else
 668                  ancestor = start.getCommonAncestor( end );
 669  
 670              return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
 671          },
 672  
 673          /**

 674           * Transforms the startContainer and endContainer properties from text

 675           * nodes to element nodes, whenever possible. This is actually possible

 676           * if either of the boundary containers point to a text node, and its

 677           * offset is set to zero, or after the last char in the node.

 678           */
 679          optimize : function()
 680          {
 681              var container = this.startContainer;
 682              var offset = this.startOffset;
 683  
 684              if ( container.type != CKEDITOR.NODE_ELEMENT )
 685              {
 686                  if ( !offset )
 687                      this.setStartBefore( container );
 688                  else if ( offset >= container.getLength() )
 689                      this.setStartAfter( container );
 690              }
 691  
 692              container = this.endContainer;
 693              offset = this.endOffset;
 694  
 695              if ( container.type != CKEDITOR.NODE_ELEMENT )
 696              {
 697                  if ( !offset )
 698                      this.setEndBefore( container );
 699                  else if ( offset >= container.getLength() )
 700                      this.setEndAfter( container );
 701              }
 702          },
 703  
 704          /**

 705           * Move the range out of bookmark nodes if they'd been the container.

 706           */
 707          optimizeBookmark: function()
 708          {
 709              var startNode = this.startContainer,
 710                  endNode = this.endContainer;
 711  
 712              if ( startNode.is && startNode.is( 'span' )
 713                  && startNode.hasAttribute( '_fck_bookmark' ) )
 714                  this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
 715              if ( endNode && endNode.is && endNode.is( 'span' )
 716                  && endNode.hasAttribute( '_fck_bookmark' ) )
 717                  this.setEndAt( endNode,  CKEDITOR.POSITION_AFTER_END );
 718          },
 719  
 720          trim : function( ignoreStart, ignoreEnd )
 721          {
 722              var startContainer = this.startContainer,
 723                  startOffset = this.startOffset,
 724                  collapsed = this.collapsed;
 725              if ( ( !ignoreStart || collapsed )
 726                   && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
 727              {
 728                  // If the offset is zero, we just insert the new node before

 729                  // the start.

 730                  if ( !startOffset )
 731                  {
 732                      startOffset = startContainer.getIndex();
 733                      startContainer = startContainer.getParent();
 734                  }
 735                  // If the offset is at the end, we'll insert it after the text

 736                  // node.

 737                  else if ( startOffset >= startContainer.getLength() )
 738                  {
 739                      startOffset = startContainer.getIndex() + 1;
 740                      startContainer = startContainer.getParent();
 741                  }
 742                  // In other case, we split the text node and insert the new

 743                  // node at the split point.

 744                  else
 745                  {
 746                      var nextText = startContainer.split( startOffset );
 747  
 748                      startOffset = startContainer.getIndex() + 1;
 749                      startContainer = startContainer.getParent();
 750  
 751                      // Check all necessity of updating the end boundary.

 752                      if ( this.startContainer.equals( this.endContainer ) )
 753                          this.setEnd( nextText, this.endOffset - this.startOffset );
 754                      else if ( startContainer.equals( this.endContainer ) )
 755                          this.endOffset += 1;
 756                  }
 757  
 758                  this.setStart( startContainer, startOffset );
 759  
 760                  if ( collapsed )
 761                  {
 762                      this.collapse( true );
 763                      return;
 764                  }
 765              }
 766  
 767              var endContainer = this.endContainer;
 768              var endOffset = this.endOffset;
 769  
 770              if ( !( ignoreEnd || collapsed )
 771                   && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
 772              {
 773                  // If the offset is zero, we just insert the new node before

 774                  // the start.

 775                  if ( !endOffset )
 776                  {
 777                      endOffset = endContainer.getIndex();
 778                      endContainer = endContainer.getParent();
 779                  }
 780                  // If the offset is at the end, we'll insert it after the text

 781                  // node.

 782                  else if ( endOffset >= endContainer.getLength() )
 783                  {
 784                      endOffset = endContainer.getIndex() + 1;
 785                      endContainer = endContainer.getParent();
 786                  }
 787                  // In other case, we split the text node and insert the new

 788                  // node at the split point.

 789                  else
 790                  {
 791                      endContainer.split( endOffset );
 792  
 793                      endOffset = endContainer.getIndex() + 1;
 794                      endContainer = endContainer.getParent();
 795                  }
 796  
 797                  this.setEnd( endContainer, endOffset );
 798              }
 799          },
 800  
 801          enlarge : function( unit )
 802          {
 803              switch ( unit )
 804              {
 805                  case CKEDITOR.ENLARGE_ELEMENT :
 806  
 807                      if ( this.collapsed )
 808                          return;
 809  
 810                      // Get the common ancestor.

 811                      var commonAncestor = this.getCommonAncestor();
 812  
 813                      var body = this.document.getBody();
 814  
 815                      // For each boundary

 816                      //        a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.

 817                      //        b. Go ahead checking siblings and enlarging the boundary as much as possible until the common ancestor is not reached. After reaching the common ancestor, just save the enlargeable node to be used later.

 818  
 819                      var startTop, endTop;
 820  
 821                      var enlargeable, sibling, commonReached;
 822  
 823                      // Indicates that the node can be added only if whitespace

 824                      // is available before it.

 825                      var needsWhiteSpace = false;
 826                      var isWhiteSpace;
 827                      var siblingText;
 828  
 829                      // Process the start boundary.

 830  
 831                      var container = this.startContainer;
 832                      var offset = this.startOffset;
 833  
 834                      if ( container.type == CKEDITOR.NODE_TEXT )
 835                      {
 836                          if ( offset )
 837                          {
 838                              // Check if there is any non-space text before the

 839                              // offset. Otherwise, container is null.

 840                              container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
 841  
 842                              // If we found only whitespace in the node, it

 843                              // means that we'll need more whitespace to be able

 844                              // to expand. For example, <i> can be expanded in

 845                              // "A <i> [B]</i>", but not in "A<i> [B]</i>".

 846                              needsWhiteSpace = !!container;
 847                          }
 848  
 849                          if ( container )
 850                          {
 851                              if ( !( sibling = container.getPrevious() ) )
 852                                  enlargeable = container.getParent();
 853                          }
 854                      }
 855                      else
 856                      {
 857                          // If we have offset, get the node preceeding it as the

 858                          // first sibling to be checked.

 859                          if ( offset )
 860                              sibling = container.getChild( offset - 1 ) || container.getLast();
 861  
 862                          // If there is no sibling, mark the container to be

 863                          // enlarged.

 864                          if ( !sibling )
 865                              enlargeable = container;
 866                      }
 867  
 868                      while ( enlargeable || sibling )
 869                      {
 870                          if ( enlargeable && !sibling )
 871                          {
 872                              // If we reached the common ancestor, mark the flag

 873                              // for it.

 874                              if ( !commonReached && enlargeable.equals( commonAncestor ) )
 875                                  commonReached = true;
 876  
 877                              if ( !body.contains( enlargeable ) )
 878                                  break;
 879  
 880                              // If we don't need space or this element breaks

 881                              // the line, then enlarge it.

 882                              if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
 883                              {
 884                                  needsWhiteSpace = false;
 885  
 886                                  // If the common ancestor has been reached,

 887                                  // we'll not enlarge it immediately, but just

 888                                  // mark it to be enlarged later if the end

 889                                  // boundary also enlarges it.

 890                                  if ( commonReached )
 891                                      startTop = enlargeable;
 892                                  else
 893                                      this.setStartBefore( enlargeable );
 894                              }
 895  
 896                              sibling = enlargeable.getPrevious();
 897                          }
 898  
 899                          // Check all sibling nodes preceeding the enlargeable

 900                          // node. The node wil lbe enlarged only if none of them

 901                          // blocks it.

 902                          while ( sibling )
 903                          {
 904                              // This flag indicates that this node has

 905                              // whitespaces at the end.

 906                              isWhiteSpace = false;
 907  
 908                              if ( sibling.type == CKEDITOR.NODE_TEXT )
 909                              {
 910                                  siblingText = sibling.getText();
 911  
 912                                  if ( /[^\s\ufeff]/.test( siblingText ) )
 913                                      sibling = null;
 914  
 915                                  isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
 916                              }
 917                              else
 918                              {
 919                                  // If this is a visible element.

 920                                  // We need to check for the bookmark attribute because IE insists on

 921                                  // rendering the display:none nodes we use for bookmarks. (#3363)

 922                                  if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
 923                                  {
 924                                      // We'll accept it only if we need

 925                                      // whitespace, and this is an inline

 926                                      // element with whitespace only.

 927                                      if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
 928                                      {
 929                                          // It must contains spaces and inline elements only.

 930  
 931                                          siblingText = sibling.getText();
 932  
 933                                          if ( (/[^\s\ufeff]/).test( siblingText ) )    // Spaces + Zero Width No-Break Space (U+FEFF)
 934                                              sibling = null;
 935                                          else
 936                                          {
 937                                              var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
 938                                              for ( var i = 0, child ; child = allChildren[ i++ ] ; )
 939                                              {
 940                                                  if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
 941                                                  {
 942                                                      sibling = null;
 943                                                      break;
 944                                                  }
 945                                              }
 946                                          }
 947  
 948                                          if ( sibling )
 949                                              isWhiteSpace = !!siblingText.length;
 950                                      }
 951                                      else
 952                                          sibling = null;
 953                                  }
 954                              }
 955  
 956                              // A node with whitespaces has been found.

 957                              if ( isWhiteSpace )
 958                              {
 959                                  // Enlarge the last enlargeable node, if we

 960                                  // were waiting for spaces.

 961                                  if ( needsWhiteSpace )
 962                                  {
 963                                      if ( commonReached )
 964                                          startTop = enlargeable;
 965                                      else if ( enlargeable )
 966                                          this.setStartBefore( enlargeable );
 967                                  }
 968                                  else
 969                                      needsWhiteSpace = true;
 970                              }
 971  
 972                              if ( sibling )
 973                              {
 974                                  var next = sibling.getPrevious();
 975  
 976                                  if ( !enlargeable && !next )
 977                                  {
 978                                      // Set the sibling as enlargeable, so it's

 979                                      // parent will be get later outside this while.

 980                                      enlargeable = sibling;
 981                                      sibling = null;
 982                                      break;
 983                                  }
 984  
 985                                  sibling = next;
 986                              }
 987                              else
 988                              {
 989                                  // If sibling has been set to null, then we

 990                                  // need to stop enlarging.

 991                                  enlargeable = null;
 992                              }
 993                          }
 994  
 995                          if ( enlargeable )
 996                              enlargeable = enlargeable.getParent();
 997                      }
 998  
 999                      // Process the end boundary. This is basically the same

1000                      // code used for the start boundary, with small changes to

1001                      // make it work in the oposite side (to the right). This

1002                      // makes it difficult to reuse the code here. So, fixes to

1003                      // the above code are likely to be replicated here.

1004  
1005                      container = this.endContainer;
1006                      offset = this.endOffset;
1007  
1008                      // Reset the common variables.

1009                      enlargeable = sibling = null;
1010                      commonReached = needsWhiteSpace = false;
1011  
1012                      if ( container.type == CKEDITOR.NODE_TEXT )
1013                      {
1014                          // Check if there is any non-space text after the

1015                          // offset. Otherwise, container is null.

1016                          container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
1017  
1018                          // If we found only whitespace in the node, it

1019                          // means that we'll need more whitespace to be able

1020                          // to expand. For example, <i> can be expanded in

1021                          // "A <i> [B]</i>", but not in "A<i> [B]</i>".

1022                          needsWhiteSpace = !( container && container.getLength() );
1023  
1024                          if ( container )
1025                          {
1026                              if ( !( sibling = container.getNext() ) )
1027                                  enlargeable = container.getParent();
1028                          }
1029                      }
1030                      else
1031                      {
1032                          // Get the node right after the boudary to be checked

1033                          // first.

1034                          sibling = container.getChild( offset );
1035  
1036                          if ( !sibling )
1037                              enlargeable = container;
1038                      }
1039  
1040                      while ( enlargeable || sibling )
1041                      {
1042                          if ( enlargeable && !sibling )
1043                          {
1044                              if ( !commonReached && enlargeable.equals( commonAncestor ) )
1045                                  commonReached = true;
1046  
1047                              if ( !body.contains( enlargeable ) )
1048                                  break;
1049  
1050                              if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
1051                              {
1052                                  needsWhiteSpace = false;
1053  
1054                                  if ( commonReached )
1055                                      endTop = enlargeable;
1056                                  else if ( enlargeable )
1057                                      this.setEndAfter( enlargeable );
1058                              }
1059  
1060                              sibling = enlargeable.getNext();
1061                          }
1062  
1063                          while ( sibling )
1064                          {
1065                              isWhiteSpace = false;
1066  
1067                              if ( sibling.type == CKEDITOR.NODE_TEXT )
1068                              {
1069                                  siblingText = sibling.getText();
1070  
1071                                  if ( /[^\s\ufeff]/.test( siblingText ) )
1072                                      sibling = null;
1073  
1074                                  isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
1075                              }
1076                              else
1077                              {
1078                                  // If this is a visible element.

1079                                  // We need to check for the bookmark attribute because IE insists on

1080                                  // rendering the display:none nodes we use for bookmarks. (#3363)

1081                                  if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
1082                                  {
1083                                      // We'll accept it only if we need

1084                                      // whitespace, and this is an inline

1085                                      // element with whitespace only.

1086                                      if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
1087                                      {
1088                                          // It must contains spaces and inline elements only.

1089  
1090                                          siblingText = sibling.getText();
1091  
1092                                          if ( (/[^\s\ufeff]/).test( siblingText ) )
1093                                              sibling = null;
1094                                          else
1095                                          {
1096                                              allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
1097                                              for ( i = 0 ; child = allChildren[ i++ ] ; )
1098                                              {
1099                                                  if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
1100                                                  {
1101                                                      sibling = null;
1102                                                      break;
1103                                                  }
1104                                              }
1105                                          }
1106  
1107                                          if ( sibling )
1108                                              isWhiteSpace = !!siblingText.length;
1109                                      }
1110                                      else
1111                                          sibling = null;
1112                                  }
1113                              }
1114  
1115                              if ( isWhiteSpace )
1116                              {
1117                                  if ( needsWhiteSpace )
1118                                  {
1119                                      if ( commonReached )
1120                                          endTop = enlargeable;
1121                                      else
1122                                          this.setEndAfter( enlargeable );
1123                                  }
1124                              }
1125  
1126                              if ( sibling )
1127                              {
1128                                  next = sibling.getNext();
1129  
1130                                  if ( !enlargeable && !next )
1131                                  {
1132                                      enlargeable = sibling;
1133                                      sibling = null;
1134                                      break;
1135                                  }
1136  
1137                                  sibling = next;
1138                              }
1139                              else
1140                              {
1141                                  // If sibling has been set to null, then we

1142                                  // need to stop enlarging.

1143                                  enlargeable = null;
1144                              }
1145                          }
1146  
1147                          if ( enlargeable )
1148                              enlargeable = enlargeable.getParent();
1149                      }
1150  
1151                      // If the common ancestor can be enlarged by both boundaries, then include it also.

1152                      if ( startTop && endTop )
1153                      {
1154                          commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
1155  
1156                          this.setStartBefore( commonAncestor );
1157                          this.setEndAfter( commonAncestor );
1158                      }
1159                      break;
1160  
1161                  case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
1162                  case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
1163  
1164                      // Enlarging the start boundary.

1165                      var walkerRange = new CKEDITOR.dom.range( this.document );
1166  
1167                      body = this.document.getBody();
1168  
1169                      walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
1170                      walkerRange.setEnd( this.startContainer, this.startOffset );
1171  
1172                      var walker = new CKEDITOR.dom.walker( walkerRange ),
1173                          blockBoundary,  // The node on which the enlarging should stop.
1174                          tailBr, // In case BR as block boundary.
1175                          notBlockBoundary = CKEDITOR.dom.walker.blockBoundary(
1176                                  ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
1177                          // Record the encountered 'blockBoundary' for later use.

1178                          boundaryGuard = function( node )
1179                          {
1180                              var retval = notBlockBoundary( node );
1181                              if ( !retval )
1182                                  blockBoundary = node;
1183                              return retval;
1184                          },
1185                          // Record the encounted 'tailBr' for later use.

1186                          tailBrGuard = function( node )
1187                          {
1188                              var retval = boundaryGuard( node );
1189                              if ( !retval && node.is && node.is( 'br' ) )
1190                                  tailBr = node;
1191                              return retval;
1192                          };
1193  
1194                      walker.guard = boundaryGuard;
1195  
1196                      enlargeable = walker.lastBackward();
1197  
1198                      // It's the body which stop the enlarging if no block boundary found.

1199                      blockBoundary = blockBoundary || body;
1200  
1201                      // Start the range either after the end of found block (<p>...</p>[text)

1202                      // or at the start of block (<p>[text...), by comparing the document position

1203                      // with 'enlargeable' node.

1204                      this.setStartAt(
1205                              blockBoundary,
1206                              !blockBoundary.is( 'br' ) &&
1207                              ( !enlargeable && this.checkStartOfBlock()
1208                                || enlargeable && blockBoundary.contains( enlargeable ) ) ?
1209                                  CKEDITOR.POSITION_AFTER_START :
1210                                  CKEDITOR.POSITION_AFTER_END );
1211  
1212                      // Enlarging the end boundary.

1213                      walkerRange = this.clone();
1214                      walkerRange.collapse();
1215                      walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
1216                      walker = new CKEDITOR.dom.walker( walkerRange );
1217  
1218                      // tailBrGuard only used for on range end.

1219                      walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
1220                          tailBrGuard : boundaryGuard;
1221                      blockBoundary = null;
1222                      // End the range right before the block boundary node.

1223  
1224                      enlargeable = walker.lastForward();
1225  
1226                      // It's the body which stop the enlarging if no block boundary found.

1227                      blockBoundary = blockBoundary || body;
1228  
1229                      // Close the range either before the found block start (text]<p>...</p>) or at the block end (...text]</p>)

1230                      // by comparing the document position with 'enlargeable' node.

1231                      this.setEndAt(
1232                              blockBoundary,
1233                              ( !enlargeable && this.checkEndOfBlock()
1234                                || enlargeable && blockBoundary.contains( enlargeable ) ) ?
1235                                  CKEDITOR.POSITION_BEFORE_END :
1236                                  CKEDITOR.POSITION_BEFORE_START );
1237                      // We must include the <br> at the end of range if there's

1238                      // one and we're expanding list item contents

1239                      if ( tailBr )
1240                          this.setEndAfter( tailBr );
1241              }
1242          },
1243  
1244          /**

1245           *  Descrease the range to make sure that boundaries

1246          *  always anchor beside text nodes or innermost element.

1247           * @param {Number} mode  ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.

1248           * <dl>

1249           *      <dt>CKEDITOR.SHRINK_ELEMENT</dt>

1250           *      <dd>Shrink the range boundaries to the edge of the innermost element.</dd>

1251           *      <dt>CKEDITOR.SHRINK_TEXT</dt>

1252           *      <dd>Shrink the range boudaries to anchor by the side of enclosed text  node, range remains if there's no text nodes on boundaries at all.</dd>

1253            * </dl>

1254           * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.

1255           */
1256          shrink : function( mode, selectContents )
1257          {
1258              // Unable to shrink a collapsed range.

1259              if ( !this.collapsed )
1260              {
1261                  mode = mode || CKEDITOR.SHRINK_TEXT;
1262  
1263                  var walkerRange = this.clone();
1264  
1265                  var startContainer = this.startContainer,
1266                      endContainer = this.endContainer,
1267                      startOffset = this.startOffset,
1268                      endOffset = this.endOffset,
1269                      collapsed = this.collapsed;
1270  
1271                  // Whether the start/end boundary is moveable.

1272                  var moveStart = 1,
1273                          moveEnd = 1;
1274  
1275                  if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
1276                  {
1277                      if ( !startOffset )
1278                          walkerRange.setStartBefore( startContainer );
1279                      else if ( startOffset >= startContainer.getLength( ) )
1280                          walkerRange.setStartAfter( startContainer );
1281                      else
1282                      {
1283                          // Enlarge the range properly to avoid walker making

1284                          // DOM changes caused by triming the text nodes later.

1285                          walkerRange.setStartBefore( startContainer );
1286                          moveStart = 0;
1287                      }
1288                  }
1289  
1290                  if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
1291                  {
1292                      if ( !endOffset )
1293                          walkerRange.setEndBefore( endContainer );
1294                      else if ( endOffset >= endContainer.getLength( ) )
1295                          walkerRange.setEndAfter( endContainer );
1296                      else
1297                      {
1298                          walkerRange.setEndAfter( endContainer );
1299                          moveEnd = 0;
1300                      }
1301                  }
1302  
1303                  var walker = new CKEDITOR.dom.walker( walkerRange );
1304  
1305                  walker.evaluator = function( node )
1306                  {
1307                      return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?
1308                          CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
1309                  };
1310  
1311                  var currentElement;
1312                  walker.guard = function( node, movingOut )
1313                  {
1314                      // Stop when we're shrink in element mode while encountering a text node.

1315                      if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )
1316                          return false;
1317  
1318                      // Stop when we've already walked "through" an element.

1319                      if ( movingOut && node.equals( currentElement ) )
1320                          return false;
1321  
1322                      if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )
1323                          currentElement = node;
1324  
1325                      return true;
1326                  };
1327  
1328                  if ( moveStart )
1329                  {
1330                      var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();
1331                      textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START );
1332                  }
1333  
1334                  if ( moveEnd )
1335                  {
1336                      walker.reset();
1337                      var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
1338                      textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END );
1339                  }
1340  
1341                  return !!( moveStart || moveEnd );
1342              }
1343          },
1344  
1345          /**

1346           * Inserts a node at the start of the range. The range will be expanded

1347           * the contain the node.

1348           */
1349          insertNode : function( node )
1350          {
1351              this.optimizeBookmark();
1352              this.trim( false, true );
1353  
1354              var startContainer = this.startContainer;
1355              var startOffset = this.startOffset;
1356  
1357              var nextNode = startContainer.getChild( startOffset );
1358  
1359              if ( nextNode )
1360                  node.insertBefore( nextNode );
1361              else
1362                  startContainer.append( node );
1363  
1364              // Check if we need to update the end boundary.

1365              if ( node.getParent().equals( this.endContainer ) )
1366                  this.endOffset++;
1367  
1368              // Expand the range to embrace the new node.

1369              this.setStartBefore( node );
1370          },
1371  
1372          moveToPosition : function( node, position )
1373          {
1374              this.setStartAt( node, position );
1375              this.collapse( true );
1376          },
1377  
1378          selectNodeContents : function( node )
1379          {
1380              this.setStart( node, 0 );
1381              this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
1382          },
1383  
1384          /**

1385           * Sets the start position of a Range.

1386           * @param {CKEDITOR.dom.node} startNode The node to start the range.

1387           * @param {Number} startOffset An integer greater than or equal to zero

1388           *        representing the offset for the start of the range from the start

1389           *        of startNode.

1390           */
1391          setStart : function( startNode, startOffset )
1392          {
1393              // W3C requires a check for the new position. If it is after the end

1394              // boundary, the range should be collapsed to the new start. It seams

1395              // we will not need this check for our use of this class so we can

1396              // ignore it for now.

1397  
1398              // Fixing invalid range start inside dtd empty elements.

1399              if( startNode.type == CKEDITOR.NODE_ELEMENT
1400                  && CKEDITOR.dtd.$empty[ startNode.getName() ] )
1401                  startNode = startNode.getParent(), startOffset = startNode.getIndex();
1402  
1403              this.startContainer    = startNode;
1404              this.startOffset    = startOffset;
1405  
1406              if ( !this.endContainer )
1407              {
1408                  this.endContainer    = startNode;
1409                  this.endOffset        = startOffset;
1410              }
1411  
1412              updateCollapsed( this );
1413          },
1414  
1415          /**

1416           * Sets the end position of a Range.

1417           * @param {CKEDITOR.dom.node} endNode The node to end the range.

1418           * @param {Number} endOffset An integer greater than or equal to zero

1419           *        representing the offset for the end of the range from the start

1420           *        of endNode.

1421           */
1422          setEnd : function( endNode, endOffset )
1423          {
1424              // W3C requires a check for the new position. If it is before the start

1425              // boundary, the range should be collapsed to the new end. It seams we

1426              // will not need this check for our use of this class so we can ignore

1427              // it for now.

1428  
1429              // Fixing invalid range end inside dtd empty elements.

1430              if( endNode.type == CKEDITOR.NODE_ELEMENT
1431                  && CKEDITOR.dtd.$empty[ endNode.getName() ] )
1432                  endNode = endNode.getParent(), endOffset = endNode.getIndex() + 1;
1433  
1434              this.endContainer    = endNode;
1435              this.endOffset        = endOffset;
1436  
1437              if ( !this.startContainer )
1438              {
1439                  this.startContainer    = endNode;
1440                  this.startOffset    = endOffset;
1441              }
1442  
1443              updateCollapsed( this );
1444          },
1445  
1446          setStartAfter : function( node )
1447          {
1448              this.setStart( node.getParent(), node.getIndex() + 1 );
1449          },
1450  
1451          setStartBefore : function( node )
1452          {
1453              this.setStart( node.getParent(), node.getIndex() );
1454          },
1455  
1456          setEndAfter : function( node )
1457          {
1458              this.setEnd( node.getParent(), node.getIndex() + 1 );
1459          },
1460  
1461          setEndBefore : function( node )
1462          {
1463              this.setEnd( node.getParent(), node.getIndex() );
1464          },
1465  
1466          setStartAt : function( node, position )
1467          {
1468              switch( position )
1469              {
1470                  case CKEDITOR.POSITION_AFTER_START :
1471                      this.setStart( node, 0 );
1472                      break;
1473  
1474                  case CKEDITOR.POSITION_BEFORE_END :
1475                      if ( node.type == CKEDITOR.NODE_TEXT )
1476                          this.setStart( node, node.getLength() );
1477                      else
1478                          this.setStart( node, node.getChildCount() );
1479                      break;
1480  
1481                  case CKEDITOR.POSITION_BEFORE_START :
1482                      this.setStartBefore( node );
1483                      break;
1484  
1485                  case CKEDITOR.POSITION_AFTER_END :
1486                      this.setStartAfter( node );
1487              }
1488  
1489              updateCollapsed( this );
1490          },
1491  
1492          setEndAt : function( node, position )
1493          {
1494              switch( position )
1495              {
1496                  case CKEDITOR.POSITION_AFTER_START :
1497                      this.setEnd( node, 0 );
1498                      break;
1499  
1500                  case CKEDITOR.POSITION_BEFORE_END :
1501                      if ( node.type == CKEDITOR.NODE_TEXT )
1502                          this.setEnd( node, node.getLength() );
1503                      else
1504                          this.setEnd( node, node.getChildCount() );
1505                      break;
1506  
1507                  case CKEDITOR.POSITION_BEFORE_START :
1508                      this.setEndBefore( node );
1509                      break;
1510  
1511                  case CKEDITOR.POSITION_AFTER_END :
1512                      this.setEndAfter( node );
1513              }
1514  
1515              updateCollapsed( this );
1516          },
1517  
1518          fixBlock : function( isStart, blockTag )
1519          {
1520              var bookmark = this.createBookmark(),
1521                  fixedBlock = this.document.createElement( blockTag );
1522  
1523              this.collapse( isStart );
1524  
1525              this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
1526  
1527              this.extractContents().appendTo( fixedBlock );
1528              fixedBlock.trim();
1529  
1530              if ( !CKEDITOR.env.ie )
1531                  fixedBlock.appendBogus();
1532  
1533              this.insertNode( fixedBlock );
1534  
1535              this.moveToBookmark( bookmark );
1536  
1537              return fixedBlock;
1538          },
1539  
1540          splitBlock : function( blockTag )
1541          {
1542              var startPath    = new CKEDITOR.dom.elementPath( this.startContainer ),
1543                  endPath        = new CKEDITOR.dom.elementPath( this.endContainer );
1544  
1545              var startBlockLimit    = startPath.blockLimit,
1546                  endBlockLimit    = endPath.blockLimit;
1547  
1548              var startBlock    = startPath.block,
1549                  endBlock    = endPath.block;
1550  
1551              var elementPath = null;
1552              // Do nothing if the boundaries are in different block limits.

1553              if ( !startBlockLimit.equals( endBlockLimit ) )
1554                  return null;
1555  
1556              // Get or fix current blocks.

1557              if ( blockTag != 'br' )
1558              {
1559                  if ( !startBlock )
1560                  {
1561                      startBlock = this.fixBlock( true, blockTag );
1562                      endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
1563                  }
1564  
1565                  if ( !endBlock )
1566                      endBlock = this.fixBlock( false, blockTag );
1567              }
1568  
1569              // Get the range position.

1570              var isStartOfBlock = startBlock && this.checkStartOfBlock(),
1571                  isEndOfBlock = endBlock && this.checkEndOfBlock();
1572  
1573              // Delete the current contents.

1574              // TODO: Why is 2.x doing CheckIsEmpty()?

1575              this.deleteContents();
1576  
1577              if ( startBlock && startBlock.equals( endBlock ) )
1578              {
1579                  if ( isEndOfBlock )
1580                  {
1581                      elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1582                      this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
1583                      endBlock = null;
1584                  }
1585                  else if ( isStartOfBlock )
1586                  {
1587                      elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1588                      this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
1589                      startBlock = null;
1590                  }
1591                  else
1592                  {
1593                      endBlock = this.splitElement( startBlock );
1594  
1595                      // In Gecko, the last child node must be a bogus <br>.

1596                      // Note: bogus <br> added under <ul> or <ol> would cause

1597                      // lists to be incorrectly rendered.

1598                      if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
1599                          startBlock.appendBogus() ;
1600                  }
1601              }
1602  
1603              return {
1604                  previousBlock : startBlock,
1605                  nextBlock : endBlock,
1606                  wasStartOfBlock : isStartOfBlock,
1607                  wasEndOfBlock : isEndOfBlock,
1608                  elementPath : elementPath
1609              };
1610          },
1611  
1612          /**

1613           * Branch the specified element from the collapsed range position and

1614           * place the caret between the two result branches.

1615           * Note: The range must be collapsed and been enclosed by this element.

1616           * @param {CKEDITOR.dom.element} element

1617           * @return {CKEDITOR.dom.element} Root element of the new branch after the split.

1618           */
1619          splitElement : function( toSplit )
1620          {
1621              if ( !this.collapsed )
1622                  return null;
1623  
1624              // Extract the contents of the block from the selection point to the end

1625              // of its contents.

1626              this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END );
1627              var documentFragment = this.extractContents();
1628  
1629              // Duplicate the element after it.

1630              var clone = toSplit.clone( false );
1631  
1632              // Place the extracted contents into the duplicated element.

1633              documentFragment.appendTo( clone );
1634              clone.insertAfter( toSplit );
1635              this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END );
1636              return clone;
1637          },
1638  
1639          /**

1640           * Check whether a range boundary is at the inner boundary of a given

1641           * element.

1642           * @param {CKEDITOR.dom.element} element The target element to check.

1643           * @param {Number} checkType The boundary to check for both the range

1644           *        and the element. It can be CKEDITOR.START or CKEDITOR.END.

1645           * @returns {Boolean} "true" if the range boundary is at the inner

1646           *        boundary of the element.

1647           */
1648          checkBoundaryOfElement : function( element, checkType )
1649          {
1650              var checkStart = ( checkType == CKEDITOR.START );
1651  
1652              // Create a copy of this range, so we can manipulate it for our checks.

1653              var walkerRange = this.clone();
1654  
1655              // Collapse the range at the proper size.

1656              walkerRange.collapse( checkStart );
1657  
1658              // Expand the range to element boundary.

1659              walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ]
1660               ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );
1661  
1662              // Create the walker, which will check if we have anything useful

1663              // in the range.

1664              var walker = new CKEDITOR.dom.walker( walkerRange );
1665              walker.evaluator = elementBoundaryEval;
1666  
1667              return walker[ checkStart ? 'checkBackward' : 'checkForward' ]();
1668          },
1669  
1670          // Calls to this function may produce changes to the DOM. The range may

1671          // be updated to reflect such changes.

1672          checkStartOfBlock : function()
1673          {
1674              var startContainer = this.startContainer,
1675                  startOffset = this.startOffset;
1676  
1677              // If the starting node is a text node, and non-empty before the offset,

1678              // then we're surely not at the start of block.

1679              if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
1680              {
1681                  var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
1682                  if ( textBefore.length )
1683                      return false;
1684              }
1685  
1686              // Antecipate the trim() call here, so the walker will not make

1687              // changes to the DOM, which would not get reflected into this

1688              // range otherwise.

1689              this.trim();
1690  
1691              // We need to grab the block element holding the start boundary, so

1692              // let's use an element path for it.

1693              var path = new CKEDITOR.dom.elementPath( this.startContainer );
1694  
1695              // Creates a range starting at the block start until the range start.

1696              var walkerRange = this.clone();
1697              walkerRange.collapse( true );
1698              walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
1699  
1700              var walker = new CKEDITOR.dom.walker( walkerRange );
1701              walker.evaluator = getCheckStartEndBlockEvalFunction( true );
1702  
1703              return walker.checkBackward();
1704          },
1705  
1706          checkEndOfBlock : function()
1707          {
1708              var endContainer = this.endContainer,
1709                  endOffset = this.endOffset;
1710  
1711              // If the ending node is a text node, and non-empty after the offset,

1712              // then we're surely not at the end of block.

1713              if ( endContainer.type == CKEDITOR.NODE_TEXT )
1714              {
1715                  var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
1716                  if ( textAfter.length )
1717                      return false;
1718              }
1719  
1720              // Antecipate the trim() call here, so the walker will not make

1721              // changes to the DOM, which would not get reflected into this

1722              // range otherwise.

1723              this.trim();
1724  
1725              // We need to grab the block element holding the start boundary, so

1726              // let's use an element path for it.

1727              var path = new CKEDITOR.dom.elementPath( this.endContainer );
1728  
1729              // Creates a range starting at the block start until the range start.

1730              var walkerRange = this.clone();
1731              walkerRange.collapse( false );
1732              walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
1733  
1734              var walker = new CKEDITOR.dom.walker( walkerRange );
1735              walker.evaluator = getCheckStartEndBlockEvalFunction( false );
1736  
1737              return walker.checkForward();
1738          },
1739  
1740          /**

1741           * Moves the range boundaries to the first/end editing point inside an

1742           * element. For example, in an element tree like

1743           * "&lt;p&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;", the start editing point is

1744           * "&lt;p&gt;&lt;b&gt;&lt;i&gt;^&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;" (inside &lt;i&gt;).

1745           * @param {CKEDITOR.dom.element} el The element into which look for the

1746           *        editing spot.

1747           * @param {Boolean} isMoveToEnd Whether move to the end editable position.

1748           */
1749          moveToElementEditablePosition : function( el, isMoveToEnd )
1750          {
1751              var isEditable;
1752  
1753              // Empty elements are rejected.

1754              if ( CKEDITOR.dtd.$empty[ el.getName() ] )
1755                  return false;
1756  
1757              while ( el && el.type == CKEDITOR.NODE_ELEMENT )
1758              {
1759                  isEditable = el.isEditable();
1760  
1761                  // If an editable element is found, move inside it.

1762                  if ( isEditable )
1763                      this.moveToPosition( el, isMoveToEnd ?
1764                                               CKEDITOR.POSITION_BEFORE_END :
1765                                               CKEDITOR.POSITION_AFTER_START );
1766                  // Stop immediately if we've found a non editable inline element (e.g <img>).

1767                  else if ( CKEDITOR.dtd.$inline[ el.getName() ] )
1768                  {
1769                      this.moveToPosition( el, isMoveToEnd ?
1770                                               CKEDITOR.POSITION_AFTER_END :
1771                                               CKEDITOR.POSITION_BEFORE_START );
1772                      return true;
1773                  }
1774  
1775                  // Non-editable non-inline elements are to be bypassed, getting the next one.

1776                  if ( CKEDITOR.dtd.$empty[ el.getName() ] )
1777                      el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );
1778                  else
1779                      el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );
1780  
1781                  // Stop immediately if we've found a text node.

1782                  if ( el && el.type == CKEDITOR.NODE_TEXT )
1783                  {
1784                      this.moveToPosition( el, isMoveToEnd ?
1785                                               CKEDITOR.POSITION_AFTER_END :
1786                                               CKEDITOR.POSITION_BEFORE_START );
1787                      return true;
1788                  }
1789              }
1790  
1791              return isEditable;
1792          },
1793  
1794          /**

1795           *@see {CKEDITOR.dom.range.moveToElementEditablePosition}

1796           */
1797          moveToElementEditStart : function( target )
1798          {
1799              return this.moveToElementEditablePosition( target );
1800          },
1801  
1802          /**

1803           *@see {CKEDITOR.dom.range.moveToElementEditablePosition}

1804           */
1805          moveToElementEditEnd : function( target )
1806          {
1807              return this.moveToElementEditablePosition( target, true );
1808          },
1809  
1810          /**

1811           * Get the single node enclosed within the range if there's one.

1812           */
1813          getEnclosedNode : function()
1814          {
1815              var walkerRange = this.clone();
1816  
1817              // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780)

1818              walkerRange.optimize();
1819              if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT
1820                      || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT )
1821                  return null;
1822  
1823              var walker = new CKEDITOR.dom.walker( walkerRange ),
1824                  isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
1825                  isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
1826                  evaluator = function( node )
1827                  {
1828                      return isNotWhitespaces( node ) && isNotBookmarks( node );
1829                  };
1830              walkerRange.evaluator = evaluator;
1831              var node = walker.next();
1832              walker.reset();
1833              return node && node.equals( walker.previous() ) ? node : null;
1834          },
1835  
1836          getTouchedStartNode : function()
1837          {
1838              var container = this.startContainer ;
1839  
1840              if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1841                  return container ;
1842  
1843              return container.getChild( this.startOffset ) || container ;
1844          },
1845  
1846          getTouchedEndNode : function()
1847          {
1848              var container = this.endContainer ;
1849  
1850              if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1851                  return container ;
1852  
1853              return container.getChild( this.endOffset - 1 ) || container ;
1854          }
1855      };
1856  })();
1857  
1858  CKEDITOR.POSITION_AFTER_START    = 1;    // <element>^contents</element>        "^text"

1859  CKEDITOR.POSITION_BEFORE_END    = 2;    // <element>contents^</element>        "text^"

1860  CKEDITOR.POSITION_BEFORE_START    = 3;    // ^<element>contents</element>        ^"text"

1861  CKEDITOR.POSITION_AFTER_END        = 4;    // <element>contents</element>^        "text"

1862  
1863  CKEDITOR.ENLARGE_ELEMENT = 1;
1864  CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
1865  CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
1866  
1867  /**

1868   * Check boundary types.

1869   * @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement

1870   */
1871  CKEDITOR.START = 1;
1872  CKEDITOR.END = 2;
1873  CKEDITOR.STARTEND = 3;
1874  
1875  /**

1876   * Shrink range types.

1877   * @see CKEDITOR.dom.range.prototype.shrink

1878   */
1879  CKEDITOR.SHRINK_ELEMENT = 1;
1880  CKEDITOR.SHRINK_TEXT = 2;


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7