text: ﻿/*
   * FCKeditor - The text editor for Internet - http://www.fckeditor.net
   * Copyright (C) 2003-2008 Frederico Caldeira Knabben
   *
   * == BEGIN LICENSE ==
   *
   * Licensed under the terms of any of the following licenses at your
   * choice:
   *
   *  - GNU General Public License Version 2 or later (the "GPL")
   *    http://www.gnu.org/licenses/gpl.html
   *
   *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
   *    http://www.gnu.org/licenses/lgpl.html
   *
   *  - Mozilla Public License Version 1.1 or later (the "MPL")
   *    http://www.mozilla.org/MPL/MPL-1.1.html
   *
   * == END LICENSE ==
   *
   * This class can be used to interate through nodes inside a range.
   *
   * During interation, the provided range can become invalid, due to document
   * mutations, so CreateBookmark() used to restore it after processing, if
   * needed.
   */
  
  var FCKDomRangeIterator = function (range) {
    /**
     * The FCKDomRange object that marks the interation boundaries.
     */
    this.Range = range;
  
    /**
     * Indicates that <br> elements must be used as paragraph boundaries.
     */
    this.ForceBrBreak = false;
  
    /**
     * Guarantees that the iterator will always return "real" block elements.
     * If "false", elements like <li>, <th> and <td> are returned. If "true", a
     * dedicated block element block element will be created inside those
     * elements to hold the selected content.
     */
    this.EnforceRealBlocks = false;
  };
  
  FCKDomRangeIterator.CreateFromSelection = function (targetWindow) {
    var range = new FCKDomRange(targetWindow);
    range.MoveToSelection();
    return new FCKDomRangeIterator(range);
  };
  
  FCKDomRangeIterator.prototype = {
    /**
     * Get the next paragraph element. It automatically breaks the document
     * when necessary to generate block elements for the paragraphs.
     */
    GetNextParagraph: function () {
      // The block element to be returned.
      var block;
  
      // The range object used to identify the paragraph contents.
      var range;
  
      // Indicated that the current element in the loop is the last one.
      var isLast;
  
      // Instructs to cleanup remaining BRs.
      var removePreviousBr;
      var removeLastBr;
  
      var boundarySet = this.ForceBrBreak
        ? FCKListsLib.ListBoundaries
        : FCKListsLib.BlockBoundaries;
  
      // This is the first iteration. Let's initialize it.
      if (!this._LastNode) {
        var range = this.Range.Clone();
        range.Expand(this.ForceBrBreak ? "list_contents" : "block_contents");
  
        this._NextNode = range.GetTouchedStartNode();
        this._LastNode = range.GetTouchedEndNode();
  
        // Let's reuse this variable.
        range = null;
      }
  
      var currentNode = this._NextNode;
      var lastNode = this._LastNode;
  
      this._NextNode = null;
  
      while (currentNode) {
        // closeRange indicates that a paragraph boundary has been found,
        // so the range can be closed.
        var closeRange = false;
  
        // includeNode indicates that the current node is good to be part
        // of the range. By default, any non-element node is ok for it.
        var includeNode = currentNode.nodeType != 1;
  
        var continueFromSibling = false;
  
        // If it is an element node, let's check if it can be part of the
        // range.
        if (!includeNode) {
          var nodeName = currentNode.nodeName.toLowerCase();
  
          if (
            boundarySet[nodeName] &&
            (!FCKBrowserInfo.IsIE || currentNode.scopeName == "HTML")
          ) {
            // <br> boundaries must be part of the range. It will
            // happen only if ForceBrBreak.
            if (nodeName == "br") includeNode = true;
            else if (
              !range &&
              currentNode.childNodes.length == 0 &&
              nodeName != "hr"
            ) {
              // If we have found an empty block, and haven't started
              // the range yet, it means we must return this block.
              block = currentNode;
              isLast = currentNode == lastNode;
              break;
            }
  
            // The range must finish right before the boundary,
            // including possibly skipped empty spaces. (#1603)
            if (range) {
              range.SetEnd(currentNode, 3, true);
  
              // The found boundary must be set as the next one at this
              // point. (#1717)
              if (nodeName != "br") this._NextNode = currentNode;
            }
  
            closeRange = true;
          } else {
            // If we have child nodes, let's check them.
            if (currentNode.firstChild) {
              // If we don't have a range yet, let's start it.
              if (!range) {
                range = new FCKDomRange(this.Range.Window);
                range.SetStart(currentNode, 3, true);
              }
  
              currentNode = currentNode.firstChild;
              continue;
            }
            includeNode = true;
          }
        } else if (currentNode.nodeType == 3) {
          // Ignore normal whitespaces (i.e. not including &nbsp; or
          // other unicode whitespaces) before/after a block node.
          if (/^[\r\n\t ]+$/.test(currentNode.nodeValue)) includeNode = false;
        }
  
        // The current node is good to be part of the range and we are
        // starting a new range, initialize it first.
        if (includeNode && !range) {
          range = new FCKDomRange(this.Range.Window);
          range.SetStart(currentNode, 3, true);
        }
  
        // The last node has been found.
        isLast = (!closeRange || includeNode) && currentNode == lastNode;
        //			isLast = ( currentNode == lastNode && ( currentNode.nodeType != 1 || currentNode.childNodes.length == 0 ) ) ;
  
        // If we are in an element boundary, let's check if it is time
        // to close the range, otherwise we include the parent within it.
        if (range && !closeRange) {
          while (!currentNode.nextSibling && !isLast) {
            var parentNode = currentNode.parentNode;
  
            if (boundarySet[parentNode.nodeName.toLowerCase()]) {
              closeRange = true;
              isLast = isLast || parentNode == lastNode;
              break;
            }
  
            currentNode = parentNode;
            includeNode = true;
            isLast = currentNode == lastNode;
            continueFromSibling = true;
          }
        }
  
        // Now finally include the node.
        if (includeNode) range.SetEnd(currentNode, 4, true);
  
        // We have found a block boundary. Let's close the range and move out of the
        // loop.
        if ((closeRange || isLast) && range) {
          range._UpdateElementInfo();
  
          if (
            range.StartNode == range.EndNode &&
            range.StartNode.parentNode == range.StartBlockLimit &&
            range.StartNode.getAttribute &&
            range.StartNode.getAttribute("_fck_bookmark")
          )
            range = null;
          else break;
        }
  
        if (isLast) break;
  
        currentNode = FCKDomTools.GetNextSourceNode(
          currentNode,
          continueFromSibling,
          null,
          lastNode
        );
      }
  
      // Now, based on the processed range, look for (or create) the block to be returned.
      if (!block) {
        // If no range has been found, this is the end.
        if (!range) {
          this._NextNode = null;
          return null;
        }
  
        block = range.StartBlock;
  
        if (
          !block &&
          !this.EnforceRealBlocks &&
          range.StartBlockLimit.nodeName.IEquals("DIV", "TH", "TD") &&
          range.CheckStartOfBlock() &&
          range.CheckEndOfBlock()
        ) {
          block = range.StartBlockLimit;
        } else if (
          !block ||
          (this.EnforceRealBlocks && block.nodeName.toLowerCase() == "li")
        ) {
          // Create the fixed block.
          block = this.Range.Window.document.createElement(
            FCKConfig.EnterMode == "p" ? "p" : "div"
          );
  
          // Move the contents of the temporary range to the fixed block.
          range.ExtractContents().AppendTo(block);
          FCKDomTools.TrimNode(block);
  
          // Insert the fixed block into the DOM.
          range.InsertNode(block);
  
          removePreviousBr = true;
          removeLastBr = true;
        } else if (block.nodeName.toLowerCase() != "li") {
          // If the range doesn't includes the entire contents of the
          // block, we must split it, isolating the range in a dedicated
          // block.
          if (!range.CheckStartOfBlock() || !range.CheckEndOfBlock()) {
            // The resulting block will be a clone of the current one.
            block = block.cloneNode(false);
  
            // Extract the range contents, moving it to the new block.
            range.ExtractContents().AppendTo(block);
            FCKDomTools.TrimNode(block);
  
            // Split the block. At this point, the range will be in the
            // right position for our intents.
            var splitInfo = range.SplitBlock();
  
            removePreviousBr = !splitInfo.WasStartOfBlock;
            removeLastBr = !splitInfo.WasEndOfBlock;
  
            // Insert the new block into the DOM.
            range.InsertNode(block);
          }
        } else if (!isLast) {
          // LIs are returned as is, with all their children (due to the
          // nested lists). But, the next node is the node right after
          // the current range, which could be an <li> child (nested
          // lists) or the next sibling <li>.
  
          this._NextNode =
            block == lastNode
              ? null
              : FCKDomTools.GetNextSourceNode(
                  range.EndNode,
                  true,
                  null,
                  lastNode
                );
          return block;
        }
      }
  
      if (removePreviousBr) {
        var previousSibling = block.previousSibling;
        if (previousSibling && previousSibling.nodeType == 1) {
          if (previousSibling.nodeName.toLowerCase() == "br")
            previousSibling.parentNode.removeChild(previousSibling);
          else if (
            previousSibling.lastChild &&
            previousSibling.lastChild.nodeName.IEquals("br")
          )
            previousSibling.removeChild(previousSibling.lastChild);
        }
      }
  
      if (removeLastBr) {
        var lastChild = block.lastChild;
        if (
          lastChild &&
          lastChild.nodeType == 1 &&
          lastChild.nodeName.toLowerCase() == "br"
        )
          block.removeChild(lastChild);
      }
  
      // Get a reference for the next element. This is important because the
      // above block can be removed or changed, so we can rely on it for the
      // next interation.
      if (!this._NextNode)
        this._NextNode =
          isLast || block == lastNode
            ? null
            : FCKDomTools.GetNextSourceNode(block, true, null, lastNode);
  
      return block;
    },
  };
  