text: /**
   * Range封装
   * @file
   * @module UE.dom
   * @class Range
   * @since 1.2.6.1
   */
  
  /**
   * dom操作封装
   * @unfile
   * @module UE.dom
   */
  
  /**
   * Range实现类，本类是UEditor底层核心类，封装不同浏览器之间的Range操作。
   * @unfile
   * @module UE.dom
   * @class Range
   */
  
  ;(function() {
    var guid = 0,
      fillChar = domUtils.fillChar,
      fillData;
  
    /**
       * 更新range的collapse状态
       * @param  {Range}   range    range对象
       */
    function updateCollapse(range) {
      range.collapsed =
        range.startContainer &&
        range.endContainer &&
        range.startContainer === range.endContainer &&
        range.startOffset == range.endOffset;
    }
  
    function selectOneNode(rng) {
      return (
        !rng.collapsed &&
        rng.startContainer.nodeType == 1 &&
        rng.startContainer === rng.endContainer &&
        rng.endOffset - rng.startOffset == 1
      );
    }
    function setEndPoint(toStart, node, offset, range) {
      //如果node是自闭合标签要处理
      if (
        node.nodeType == 1 &&
        (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])
      ) {
        offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);
        node = node.parentNode;
      }
      if (toStart) {
        range.startContainer = node;
        range.startOffset = offset;
        if (!range.endContainer) {
          range.collapse(true);
        }
      } else {
        range.endContainer = node;
        range.endOffset = offset;
        if (!range.startContainer) {
          range.collapse(false);
        }
      }
      updateCollapse(range);
      return range;
    }
  
    function execContentsAction(range, action) {
      //调整边界
      //range.includeBookmark();
      var start = range.startContainer,
        end = range.endContainer,
        startOffset = range.startOffset,
        endOffset = range.endOffset,
        doc = range.document,
        frag = doc.createDocumentFragment(),
        tmpStart,
        tmpEnd;
      if (start.nodeType == 1) {
        start =
          start.childNodes[startOffset] ||
          (tmpStart = start.appendChild(doc.createTextNode("")));
      }
      if (end.nodeType == 1) {
        end =
          end.childNodes[endOffset] ||
          (tmpEnd = end.appendChild(doc.createTextNode("")));
      }
      if (start === end && start.nodeType == 3) {
        frag.appendChild(
          doc.createTextNode(
            start.substringData(startOffset, endOffset - startOffset)
          )
        );
        //is not clone
        if (action) {
          start.deleteData(startOffset, endOffset - startOffset);
          range.collapse(true);
        }
        return frag;
      }
      var current,
        currentLevel,
        clone = frag,
        startParents = domUtils.findParents(start, true),
        endParents = domUtils.findParents(end, true);
      for (var i = 0; startParents[i] == endParents[i]; ) {
        i++;
      }
      for (var j = i, si; (si = startParents[j]); j++) {
        current = si.nextSibling;
        if (si == start) {
          if (!tmpStart) {
            if (range.startContainer.nodeType == 3) {
              clone.appendChild(
                doc.createTextNode(start.nodeValue.slice(startOffset))
              );
              //is not clone
              if (action) {
                start.deleteData(
                  startOffset,
                  start.nodeValue.length - startOffset
                );
              }
            } else {
              clone.appendChild(!action ? start.cloneNode(true) : start);
            }
          }
        } else {
          currentLevel = si.cloneNode(false);
          clone.appendChild(currentLevel);
        }
        while (current) {
          if (current === end || current === endParents[j]) {
            break;
          }
          si = current.nextSibling;
          clone.appendChild(!action ? current.cloneNode(true) : current);
          current = si;
        }
        clone = currentLevel;
      }
      clone = frag;
      if (!startParents[i]) {
        clone.appendChild(startParents[i - 1].cloneNode(false));
        clone = clone.firstChild;
      }
      for (var j = i, ei; (ei = endParents[j]); j++) {
        current = ei.previousSibling;
        if (ei == end) {
          if (!tmpEnd && range.endContainer.nodeType == 3) {
            clone.appendChild(
              doc.createTextNode(end.substringData(0, endOffset))
            );
            //is not clone
            if (action) {
              end.deleteData(0, endOffset);
            }
          }
        } else {
          currentLevel = ei.cloneNode(false);
          clone.appendChild(currentLevel);
        }
        //如果两端同级，右边第一次已经被开始做了
        if (j != i || !startParents[i]) {
          while (current) {
            if (current === start) {
              break;
            }
            ei = current.previousSibling;
            clone.insertBefore(
              !action ? current.cloneNode(true) : current,
              clone.firstChild
            );
            current = ei;
          }
        }
        clone = currentLevel;
      }
      if (action) {
        range
          .setStartBefore(
            !endParents[i]
              ? endParents[i - 1]
              : !startParents[i] ? startParents[i - 1] : endParents[i]
          )
          .collapse(true);
      }
      tmpStart && domUtils.remove(tmpStart);
      tmpEnd && domUtils.remove(tmpEnd);
      return frag;
    }
  
    /**
       * 创建一个跟document绑定的空的Range实例
       * @constructor
       * @param { Document } document 新建的选区所属的文档对象
       */
  
    /**
       * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点
       */
  
    /**
       * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点，
       *                              该值就是childNodes中的第几个节点， 如果是文本节点就是文本内容的第几个字符
       */
  
    /**
       * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点
       */
  
    /**
       * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点，
       *                              该值就是childNodes中的第几个节点， 如果是文本节点就是文本内容的第几个字符
       */
  
    /**
       * @property { Boolean } collapsed 当前Range是否闭合
       * @default true
       * @remind Range是闭合的时候， startContainer === endContainer && startOffset === endOffset
       */
  
    /**
       * @property { Document } document 当前Range所属的Document对象
       * @remind 不同range的的document属性可以是不同的
       */
    var Range = (dom.Range = function(document) {
      var me = this;
      me.startContainer = me.startOffset = me.endContainer = me.endOffset = null;
      me.document = document;
      me.collapsed = true;
    });
  
    /**
       * 删除fillData
       * @param doc
       * @param excludeNode
       */
    function removeFillData(doc, excludeNode) {
      try {
        if (fillData && domUtils.inDoc(fillData, doc)) {
          if (!fillData.nodeValue.replace(fillCharReg, "").length) {
            var tmpNode = fillData.parentNode;
            domUtils.remove(fillData);
            while (
              tmpNode &&
              domUtils.isEmptyInlineElement(tmpNode) &&
              //safari的contains有bug
              (browser.safari
                ? !(
                    domUtils.getPosition(tmpNode, excludeNode) &
                    domUtils.POSITION_CONTAINS
                  )
                : !tmpNode.contains(excludeNode))
            ) {
              fillData = tmpNode.parentNode;
              domUtils.remove(tmpNode);
              tmpNode = fillData;
            }
          } else {
            fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, "");
          }
        }
      } catch (e) {}
    }
  
    /**
       * @param node
       * @param dir
       */
    function mergeSibling(node, dir) {
      var tmpNode;
      node = node[dir];
      while (node && domUtils.isFillChar(node)) {
        tmpNode = node[dir];
        domUtils.remove(node);
        node = tmpNode;
      }
    }
  
    Range.prototype = {
      /**
           * 克隆选区的内容到一个DocumentFragment里
           * @method cloneContents
           * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null， 否则， 返回包含所clone内容的DocumentFragment元素
           * @example
           * ```html
           * <body>
           *      <!-- 中括号表示选区 -->
           *      <b>x<i>x[x</i>xx]x</b>
           *
           *      <script>
           *          //range是已选中的选区
           *          var fragment = range.cloneContents(),
           *              node = document.createElement("div");
           *
           *          node.appendChild( fragment );
           *
           *          //output: <i>x</i>xx
           *          console.log( node.innerHTML );
           *
           *      </script>
           * </body>
           * ```
           */
      cloneContents: function() {
        return this.collapsed ? null : execContentsAction(this, 0);
      },
  
      /**
           * 删除当前选区范围中的所有内容
           * @method deleteContents
           * @remind 执行完该操作后， 当前Range对象变成了闭合状态
           * @return { UE.dom.Range } 当前操作的Range对象
           * @example
           * ```html
           * <body>
           *      <!-- 中括号表示选区 -->
           *      <b>x<i>x[x</i>xx]x</b>
           *
           *      <script>
           *          //range是已选中的选区
           *          range.deleteContents();
           *
           *          //竖线表示闭合后的选区位置
           *          //output: <b>x<i>x</i>|x</b>
           *          console.log( document.body.innerHTML );
           *
           *          //此时， range的各项属性为
           *          //output: B
           *          console.log( range.startContainer.tagName );
           *          //output: 2
           *          console.log( range.startOffset );
           *          //output: B
           *          console.log( range.endContainer.tagName );
           *          //output: 2
           *          console.log( range.endOffset );
           *          //output: true
           *          console.log( range.collapsed );
           *
           *      </script>
           * </body>
           * ```
           */
      deleteContents: function() {
        var txt;
        if (!this.collapsed) {
          execContentsAction(this, 1);
        }
        if (browser.webkit) {
          txt = this.startContainer;
          if (txt.nodeType == 3 && !txt.nodeValue.length) {
            this.setStartBefore(txt).collapse(true);
            domUtils.remove(txt);
          }
        }
        return this;
      },
  
      /**
           * 将当前选区的内容提取到一个DocumentFragment里
           * @method extractContents
           * @remind 执行该操作后， 选区将变成闭合状态
           * @warning 执行该操作后， 原来选区所选中的内容将从dom树上剥离出来
           * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象
           * @example
           * ```html
           * <body>
           *      <!-- 中括号表示选区 -->
           *      <b>x<i>x[x</i>xx]x</b>
           *
           *      <script>
           *          //range是已选中的选区
           *          var fragment = range.extractContents(),
           *              node = document.createElement( "div" );
           *
           *          node.appendChild( fragment );
           *
           *          //竖线表示闭合后的选区位置
           *
           *          //output: <b>x<i>x</i>|x</b>
           *          console.log( document.body.innerHTML );
           *          //output: <i>x</i>xx
           *          console.log( node.innerHTML );
           *
           *          //此时， range的各项属性为
           *          //output: B
           *          console.log( range.startContainer.tagName );
           *          //output: 2
           *          console.log( range.startOffset );
           *          //output: B
           *          console.log( range.endContainer.tagName );
           *          //output: 2
           *          console.log( range.endOffset );
           *          //output: true
           *          console.log( range.collapsed );
           *
           *      </script>
           * </body>
           */
      extractContents: function() {
        return this.collapsed ? null : execContentsAction(this, 2);
      },
  
      /**
           * 设置Range的开始容器节点和偏移量
           * @method  setStart
           * @remind 如果给定的节点是元素节点，那么offset指的是其子元素中索引为offset的元素，
           *          如果是文本节点，那么offset指的是其文本内容的第offset个字符
           * @remind 如果提供的容器节点是一个不能包含子元素的节点， 则该选区的开始容器将被设置
           *          为该节点的父节点， 此时， 其距离开始容器的偏移量也变成了该节点在其父节点
           *          中的索引
           * @param { Node } node 将被设为当前选区开始边界容器的节点对象
           * @param { int } offset 选区的开始位置偏移量
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           * <!-- 选区 -->
           * <b>xxx<i>x<span>xx</span>xx<em>xx</em>xxx</i>[xxx]</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.setStart( document.getElementsByTagName("i")[0], 1 );
           *
           *     //此时， 选区变成了
           *     //<b>xxx<i>x[<span>xx</span>xx<em>xx</em>xxx</i>xxx]</b>
           *
           * </script>
           * ```
           * @example
           * ```html
           * <!-- 选区 -->
           * <b>xxx<img>[xx]x</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.setStart( document.getElementsByTagName("img")[0], 3 );
           *
           *     //此时， 选区变成了
           *     //<b>xxx[<img>xx]x</b>
           *
           * </script>
           * ```
           */
      setStart: function(node, offset) {
        return setEndPoint(true, node, offset, this);
      },
  
      /**
           * 设置Range的结束容器和偏移量
           * @method  setEnd
           * @param { Node } node 作为当前选区结束边界容器的节点对象
           * @param { int } offset 结束边界的偏移量
           * @see UE.dom.Range:setStart(Node,int)
           * @return { UE.dom.Range } 当前range对象
           */
      setEnd: function(node, offset) {
        return setEndPoint(false, node, offset, this);
      },
  
      /**
           * 将Range开始位置设置到node节点之后
           * @method  setStartAfter
           * @remind 该操作将会把给定节点的父节点作为range的开始容器， 且偏移量是该节点在其父节点中的位置索引+1
           * @param { Node } node 选区的开始边界将紧接着该节点之后
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           * <!-- 选区示例 -->
           * <b>xx<i>xxx</i><span>xx[x</span>xxx]</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.setStartAfter( document.getElementsByTagName("i")[0] );
           *
           *     //结果选区
           *     //<b>xx<i>xxx</i>[<span>xxx</span>xxx]</b>
           *
           * </script>
           * ```
           */
      setStartAfter: function(node) {
        return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);
      },
  
      /**
           * 将Range开始位置设置到node节点之前
           * @method  setStartBefore
           * @remind 该操作将会把给定节点的父节点作为range的开始容器， 且偏移量是该节点在其父节点中的位置索引
           * @param { Node } node 新的选区开始位置在该节点之前
           * @see UE.dom.Range:setStartAfter(Node)
           * @return { UE.dom.Range } 当前range对象
           */
      setStartBefore: function(node) {
        return this.setStart(node.parentNode, domUtils.getNodeIndex(node));
      },
  
      /**
           * 将Range结束位置设置到node节点之后
           * @method  setEndAfter
           * @remind 该操作将会把给定节点的父节点作为range的结束容器， 且偏移量是该节点在其父节点中的位置索引+1
           * @param { Node } node 目标节点
           * @see UE.dom.Range:setStartAfter(Node)
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           * <!-- 选区示例 -->
           * <b>[xx<i>xxx</i><span>xx]x</span>xxx</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.setStartAfter( document.getElementsByTagName("span")[0] );
           *
           *     //结果选区
           *     //<b>[xx<i>xxx</i><span>xxx</span>]xxx</b>
           *
           * </script>
           * ```
           */
      setEndAfter: function(node) {
        return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);
      },
  
      /**
           * 将Range结束位置设置到node节点之前
           * @method  setEndBefore
           * @remind 该操作将会把给定节点的父节点作为range的结束容器， 且偏移量是该节点在其父节点中的位置索引
           * @param { Node } node 目标节点
           * @see UE.dom.Range:setEndAfter(Node)
           * @return { UE.dom.Range } 当前range对象
           */
      setEndBefore: function(node) {
        return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));
      },
  
      /**
           * 设置Range的开始位置到node节点内的第一个子节点之前
           * @method  setStartAtFirst
           * @remind 选区的开始容器将变成给定的节点， 且偏移量为0
           * @remind 如果给定的节点是元素节点， 则该节点必须是允许包含子节点的元素。
           * @param { Node } node 目标节点
           * @see UE.dom.Range:setStartBefore(Node)
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           * <!-- 选区示例 -->
           * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.setStartAtFirst( document.getElementsByTagName("i")[0] );
           *
           *     //结果选区
           *     //<b>xx<i>[xxx</i><span>xx]x</span>xxx</b>
           *
           * </script>
           * ```
           */
      setStartAtFirst: function(node) {
        return this.setStart(node, 0);
      },
  
      /**
           * 设置Range的开始位置到node节点内的最后一个节点之后
           * @method setStartAtLast
           * @remind 选区的开始容器将变成给定的节点， 且偏移量为该节点的子节点数
           * @remind 如果给定的节点是元素节点， 则该节点必须是允许包含子节点的元素。
           * @param { Node } node 目标节点
           * @see UE.dom.Range:setStartAtFirst(Node)
           * @return { UE.dom.Range } 当前range对象
           */
      setStartAtLast: function(node) {
        return this.setStart(
          node,
          node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
        );
      },
  
      /**
           * 设置Range的结束位置到node节点内的第一个节点之前
           * @method  setEndAtFirst
           * @param { Node } node 目标节点
           * @remind 选区的结束容器将变成给定的节点， 且偏移量为0
           * @remind node必须是一个元素节点， 且必须是允许包含子节点的元素。
           * @see UE.dom.Range:setStartAtFirst(Node)
           * @return { UE.dom.Range } 当前range对象
           */
      setEndAtFirst: function(node) {
        return this.setEnd(node, 0);
      },
  
      /**
           * 设置Range的结束位置到node节点内的最后一个节点之后
           * @method  setEndAtLast
           * @param { Node } node 目标节点
           * @remind 选区的结束容器将变成给定的节点， 且偏移量为该节点的子节点数量
           * @remind node必须是一个元素节点， 且必须是允许包含子节点的元素。
           * @see UE.dom.Range:setStartAtFirst(Node)
           * @return { UE.dom.Range } 当前range对象
           */
      setEndAtLast: function(node) {
        return this.setEnd(
          node,
          node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
        );
      },
  
      /**
           * 选中给定节点
           * @method  selectNode
           * @remind 此时， 选区的开始容器和结束容器都是该节点的父节点， 其startOffset是该节点在父节点中的位置索引，
           *          而endOffset为startOffset+1
           * @param { Node } node 需要选中的节点
           * @return { UE.dom.Range } 当前range对象，此时的range仅包含当前给定的节点对象
           * @example
           * ```html
           * <!-- 选区示例 -->
           * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.selectNode( document.getElementsByTagName("i")[0] );
           *
           *     //结果选区
           *     //<b>xx[<i>xxx</i>]<span>xxx</span>xxx</b>
           *
           * </script>
           * ```
           */
      selectNode: function(node) {
        return this.setStartBefore(node).setEndAfter(node);
      },
  
      /**
           * 选中给定节点内部的所有节点
           * @method  selectNodeContents
           * @remind 此时， 选区的开始容器和结束容器都是该节点， 其startOffset为0，
           *          而endOffset是该节点的子节点数。
           * @param { Node } node 目标节点， 当前range将包含该节点内的所有节点
           * @return { UE.dom.Range } 当前range对象， 此时range仅包含给定节点的所有子节点
           * @example
           * ```html
           * <!-- 选区示例 -->
           * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.selectNode( document.getElementsByTagName("b")[0] );
           *
           *     //结果选区
           *     //<b>[xx<i>xxx</i><span>xxx</span>xxx]</b>
           *
           * </script>
           * ```
           */
      selectNodeContents: function(node) {
        return this.setStart(node, 0).setEndAtLast(node);
      },
  
      /**
           * clone当前Range对象
           * @method  cloneRange
           * @remind 返回的range是一个全新的range对象， 其内部所有属性与当前被clone的range相同。
           * @return { UE.dom.Range } 当前range对象的一个副本
           */
      cloneRange: function() {
        var me = this;
        return new Range(me.document)
          .setStart(me.startContainer, me.startOffset)
          .setEnd(me.endContainer, me.endOffset);
      },
  
      /**
           * 向当前选区的结束处闭合选区
           * @method  collapse
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           * <!-- 选区示例 -->
           * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.collapse();
           *
           *     //结果选区
           *     //“|”表示选区已闭合
           *     //<b>xx<i>xxx</i><span>xx|x</span>xxx</b>
           *
           * </script>
           * ```
           */
  
      /**
           * 闭合当前选区，根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合，
           * 如果toStart的值为true，则向开始位置闭合， 反之，向结束位置闭合。
           * @method  collapse
           * @param { Boolean } toStart 是否向选区开始处闭合
           * @return { UE.dom.Range } 当前range对象，此时range对象处于闭合状态
           * @see UE.dom.Range:collapse()
           * @example
           * ```html
           * <!-- 选区示例 -->
           * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
           *
           * <script>
           *
           *     //执行操作
           *     range.collapse( true );
           *
           *     //结果选区
           *     //“|”表示选区已闭合
           *     //<b>xx<i>xxx</i><span>|xxx</span>xxx</b>
           *
           * </script>
           * ```
           */
      collapse: function(toStart) {
        var me = this;
        if (toStart) {
          me.endContainer = me.startContainer;
          me.endOffset = me.startOffset;
        } else {
          me.startContainer = me.endContainer;
          me.startOffset = me.endOffset;
        }
        me.collapsed = true;
        return me;
      },
  
      /**
           * 调整range的开始位置和结束位置，使其"收缩"到最小的位置
           * @method  shrinkBoundary
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           * <span>xx<b>xx[</b>xxxxx]</span> => <span>xx<b>xx</b>[xxxxx]</span>
           * ```
           *
           * @example
           * ```html
           * <!-- 选区示例 -->
           * <b>x[xx</b><i>]xxx</i>
           *
           * <script>
           *
           *     //执行收缩
           *     range.shrinkBoundary();
           *
           *     //结果选区
           *     //<b>x[xx]</b><i>xxx</i>
           * </script>
           * ```
           *
           * @example
           * ```html
           * [<b><i>xxxx</i>xxxxxxx</b>] => <b><i>[xxxx</i>xxxxxxx]</b>
           * ```
           */
  
      /**
           * 调整range的开始位置和结束位置，使其"收缩"到最小的位置，
           * 如果ignoreEnd的值为true，则忽略对结束位置的调整
           * @method  shrinkBoundary
           * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整
           * @return { UE.dom.Range } 当前range对象
           * @see UE.dom.domUtils.Range:shrinkBoundary()
           */
      shrinkBoundary: function(ignoreEnd) {
        var me = this,
          child,
          collapsed = me.collapsed;
        function check(node) {
          return (
            node.nodeType == 1 &&
            !domUtils.isBookmarkNode(node) &&
            !dtd.$empty[node.tagName] &&
            !dtd.$nonChild[node.tagName]
          );
        }
        while (
          me.startContainer.nodeType == 1 && //是element
          (child = me.startContainer.childNodes[me.startOffset]) && //子节点也是element
          check(child)
        ) {
          me.setStart(child, 0);
        }
        if (collapsed) {
          return me.collapse(true);
        }
        if (!ignoreEnd) {
          while (
            me.endContainer.nodeType == 1 && //是element
            me.endOffset > 0 && //如果是空元素就退出 endOffset=0那么endOffst-1为负值，childNodes[endOffset]报错
            (child = me.endContainer.childNodes[me.endOffset - 1]) && //子节点也是element
            check(child)
          ) {
            me.setEnd(child, child.childNodes.length);
          }
        }
        return me;
      },
  
      /**
           * 获取离当前选区内包含的所有节点最近的公共祖先节点，
           * @method  getCommonAncestor
           * @remind 返回的公共祖先节点一定不是range自身的容器节点， 但有可能是一个文本节点
           * @return { Node } 当前range对象内所有节点的公共祖先节点
           * @example
           * ```html
           * //选区示例
           * <span>xxx<b>x[x<em>xx]x</em>xxx</b>xx</span>
           * <script>
           *
           *     var node = range.getCommonAncestor();
           *
           *     //公共祖先节点是： b节点
           *     //输出： B
           *     console.log(node.tagName);
           *
           * </script>
           * ```
           */
  
      /**
           * 获取当前选区所包含的所有节点的公共祖先节点， 可以根据给定的参数 includeSelf 决定获取到
           * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点， 如果 includeSelf
           * 的取值为true， 则返回的节点可以是自身的容器节点， 否则， 则不能是容器节点
           * @method  getCommonAncestor
           * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
           * @return { Node } 当前range对象内所有节点的公共祖先节点
           * @see UE.dom.Range:getCommonAncestor()
           * @example
           * ```html
           * <body>
           *
           *     <!-- 选区示例 -->
           *     <b>xxx<i>xxxx<span>xx[x</span>xx]x</i>xxxxxxx</b>
           *
           *     <script>
           *
           *         var node = range.getCommonAncestor( false );
           *
           *         //这里的公共祖先节点是B而不是I， 是因为参数限制了获取到的节点不能是容器节点
           *         //output: B
           *         console.log( node.tagName );
           *
           *     </script>
           *
           * </body>
           * ```
           */
  
      /**
           * 获取当前选区所包含的所有节点的公共祖先节点， 可以根据给定的参数 includeSelf 决定获取到
           * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点， 如果 includeSelf
           * 的取值为true， 则返回的节点可以是自身的容器节点， 否则， 则不能是容器节点； 同时可以根据
           * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。
           * @method  getCommonAncestor
           * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
           * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点
           * @return { Node } 当前range对象内所有节点的公共祖先节点
           * @see UE.dom.Range:getCommonAncestor()
           * @see UE.dom.Range:getCommonAncestor(Boolean)
           * @example
           * ```html
           * <body>
           *
           *     <!-- 选区示例 -->
           *     <b>xxx<i>xxxx<span>x[x]x</span>xxx</i>xxxxxxx</b>
           *
           *     <script>
           *
           *         var node = range.getCommonAncestor( true, false );
           *
           *         //output: SPAN
           *         console.log( node.tagName );
           *
           *     </script>
           *
           * </body>
           * ```
           */
      getCommonAncestor: function(includeSelf, ignoreTextNode) {
        var me = this,
          start = me.startContainer,
          end = me.endContainer;
        if (start === end) {
          if (includeSelf && selectOneNode(this)) {
            start = start.childNodes[me.startOffset];
            if (start.nodeType == 1) return start;
          }
          //只有在上来就相等的情况下才会出现是文本的情况
          return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start;
        }
        return domUtils.getCommonAncestor(start, end);
      },
  
      /**
           * 调整当前Range的开始和结束边界容器，如果是容器节点是文本节点,就调整到包含该文本节点的父节点上
           * @method trimBoundary
           * @remind 该操作有可能会引起文本节点被切开
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           *
           * //选区示例
           * <b>xxx<i>[xxxxx]</i>xxx</b>
           *
           * <script>
           *     //未调整前， 选区的开始容器和结束都是文本节点
           *     //执行调整
           *     range.trimBoundary();
           *
           *     //调整之后， 容器节点变成了i节点
           *     //<b>xxx[<i>xxxxx</i>]xxx</b>
           * </script>
           * ```
           */
  
      /**
           * 调整当前Range的开始和结束边界容器，如果是容器节点是文本节点,就调整到包含该文本节点的父节点上，
           * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整
           * @method trimBoundary
           * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           *
           * //选区示例
           * <b>xxx<i>[xxxxx]</i>xxx</b>
           *
           * <script>
           *     //未调整前， 选区的开始容器和结束都是文本节点
           *     //执行调整
           *     range.trimBoundary( true );
           *
           *     //调整之后， 开始容器节点变成了i节点
           *     //但是， 结束容器没有发生变化
           *     //<b>xxx[<i>xxxxx]</i>xxx</b>
           * </script>
           * ```
           */
      trimBoundary: function(ignoreEnd) {
        this.txtToElmBoundary();
        var start = this.startContainer,
          offset = this.startOffset,
          collapsed = this.collapsed,
          end = this.endContainer;
        if (start.nodeType == 3) {
          if (offset == 0) {
            this.setStartBefore(start);
          } else {
            if (offset >= start.nodeValue.length) {
              this.setStartAfter(start);
            } else {
              var textNode = domUtils.split(start, offset);
              //跟新结束边界
              if (start === end) {
                this.setEnd(textNode, this.endOffset - offset);
              } else if (start.parentNode === end) {
                this.endOffset += 1;
              }
              this.setStartBefore(textNode);
            }
          }
          if (collapsed) {
            return this.collapse(true);
          }
        }
        if (!ignoreEnd) {
          offset = this.endOffset;
          end = this.endContainer;
          if (end.nodeType == 3) {
            if (offset == 0) {
              this.setEndBefore(end);
            } else {
              offset < end.nodeValue.length && domUtils.split(end, offset);
              this.setEndAfter(end);
            }
          }
        }
        return this;
      },
  
      /**
           * 如果选区在文本的边界上，就扩展选区到文本的父节点上, 如果当前选区是闭合的， 则什么也不做
           * @method txtToElmBoundary
           * @remind 该操作不会修改dom节点
           * @return { UE.dom.Range } 当前range对象
           */
  
      /**
           * 如果选区在文本的边界上，就扩展选区到文本的父节点上, 如果当前选区是闭合的， 则根据参数项
           * ignoreCollapsed 的值决定是否执行该调整
           * @method txtToElmBoundary
           * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态， 如果该参数取值为true， 则
           *                      不论选区是否闭合， 都会执行该操作， 反之， 则不会对闭合的选区执行该操作
           * @return { UE.dom.Range } 当前range对象
           */
      txtToElmBoundary: function(ignoreCollapsed) {
        function adjust(r, c) {
          var container = r[c + "Container"],
            offset = r[c + "Offset"];
          if (container.nodeType == 3) {
            if (!offset) {
              r[
                "set" +
                  c.replace(/(\w)/, function(a) {
                    return a.toUpperCase();
                  }) +
                  "Before"
              ](container);
            } else if (offset >= container.nodeValue.length) {
              r[
                "set" +
                  c.replace(/(\w)/, function(a) {
                    return a.toUpperCase();
                  }) +
                  "After"
              ](container);
            }
          }
        }
  
        if (ignoreCollapsed || !this.collapsed) {
          adjust(this, "start");
          adjust(this, "end");
        }
        return this;
      },
  
      /**
           * 在当前选区的开始位置前插入节点，新插入的节点会被该range包含
           * @method  insertNode
           * @param { Node } node 需要插入的节点
           * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点
           * @return { UE.dom.Range } 当前range对象
           */
      insertNode: function(node) {
        var first = node,
          length = 1;
        if (node.nodeType == 11) {
          first = node.firstChild;
          length = node.childNodes.length;
        }
        this.trimBoundary(true);
        var start = this.startContainer,
          offset = this.startOffset;
        var nextNode = start.childNodes[offset];
        if (nextNode) {
          start.insertBefore(node, nextNode);
        } else {
          start.appendChild(node);
        }
        if (first.parentNode === this.endContainer) {
          this.endOffset = this.endOffset + length;
        }
        return this.setStartBefore(first);
      },
  
      /**
           * 闭合选区到当前选区的开始位置， 并且定位光标到闭合后的位置
           * @method  setCursor
           * @return { UE.dom.Range } 当前range对象
           * @see UE.dom.Range:collapse()
           */
  
      /**
           * 闭合选区，可以根据参数toEnd的值控制选区是向前闭合还是向后闭合， 并且定位光标到闭合后的位置。
           * @method  setCursor
           * @param { Boolean } toEnd 是否向后闭合， 如果为true， 则闭合选区时， 将向结束容器方向闭合，
           *                      反之，则向开始容器方向闭合
           * @return { UE.dom.Range } 当前range对象
           * @see UE.dom.Range:collapse(Boolean)
           */
      setCursor: function(toEnd, noFillData) {
        return this.collapse(!toEnd).select(noFillData);
      },
  
      /**
           * 创建当前range的一个书签，记录下当前range的位置，方便当dom树改变时，还能找回原来的选区位置
           * @method createBookmark
           * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID，如果该值为true，则
           *                              返回标记位置的ID， 反之则返回标记位置节点的引用
           * @return { Object } 返回一个书签记录键值对， 其包含的key有： start => 开始标记的ID或者引用，
           *                          end => 结束标记的ID或引用， id => 当前标记的类型， 如果为true，则表示
           *                          返回的记录的类型为ID， 反之则为引用
           */
      createBookmark: function(serialize, same) {
        var endNode,
          startNode = this.document.createElement("span");
        startNode.style.cssText = "display:none;line-height:0px;";
        startNode.appendChild(this.document.createTextNode("\u200D"));
        startNode.id = "_baidu_bookmark_start_" + (same ? "" : guid++);
  
        if (!this.collapsed) {
          endNode = startNode.cloneNode(true);
          endNode.id = "_baidu_bookmark_end_" + (same ? "" : guid++);
        }
        this.insertNode(startNode);
        if (endNode) {
          this.collapse().insertNode(endNode).setEndBefore(endNode);
        }
        this.setStartAfter(startNode);
        return {
          start: serialize ? startNode.id : startNode,
          end: endNode ? (serialize ? endNode.id : endNode) : null,
          id: serialize
        };
      },
  
      /**
           *  调整当前range的边界到书签位置，并删除该书签对象所标记的位置内的节点
           *  @method  moveToBookmark
           *  @param { BookMark } bookmark createBookmark所创建的标签对象
           *  @return { UE.dom.Range } 当前range对象
           *  @see UE.dom.Range:createBookmark(Boolean)
           */
      moveToBookmark: function(bookmark) {
        var start = bookmark.id
          ? this.document.getElementById(bookmark.start)
          : bookmark.start,
          end = bookmark.end && bookmark.id
            ? this.document.getElementById(bookmark.end)
            : bookmark.end;
        this.setStartBefore(start);
        domUtils.remove(start);
        if (end) {
          this.setEndBefore(end);
          domUtils.remove(end);
        } else {
          this.collapse(true);
        }
        return this;
      },
  
      /**
           * 调整range的边界，使其"放大"到最近的父节点
           * @method  enlarge
           * @remind 会引起选区的变化
           * @return { UE.dom.Range } 当前range对象
           */
  
      /**
           * 调整range的边界，使其"放大"到最近的父节点，根据参数 toBlock 的取值， 可以
           * 要求扩大之后的父节点是block节点
           * @method  enlarge
           * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点
           * @return { UE.dom.Range } 当前range对象
           */
      enlarge: function(toBlock, stopFn) {
        var isBody = domUtils.isBody,
          pre,
          node,
          tmp = this.document.createTextNode("");
        if (toBlock) {
          node = this.startContainer;
          if (node.nodeType == 1) {
            if (node.childNodes[this.startOffset]) {
              pre = node = node.childNodes[this.startOffset];
            } else {
              node.appendChild(tmp);
              pre = node = tmp;
            }
          } else {
            pre = node;
          }
          while (1) {
            if (domUtils.isBlockElm(node)) {
              node = pre;
              while ((pre = node.previousSibling) && !domUtils.isBlockElm(pre)) {
                node = pre;
              }
              this.setStartBefore(node);
              break;
            }
            pre = node;
            node = node.parentNode;
          }
          node = this.endContainer;
          if (node.nodeType == 1) {
            if ((pre = node.childNodes[this.endOffset])) {
              node.insertBefore(tmp, pre);
            } else {
              node.appendChild(tmp);
            }
            pre = node = tmp;
          } else {
            pre = node;
          }
          while (1) {
            if (domUtils.isBlockElm(node)) {
              node = pre;
              while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) {
                node = pre;
              }
              this.setEndAfter(node);
              break;
            }
            pre = node;
            node = node.parentNode;
          }
          if (tmp.parentNode === this.endContainer) {
            this.endOffset--;
          }
          domUtils.remove(tmp);
        }
  
        // 扩展边界到最大
        if (!this.collapsed) {
          while (this.startOffset == 0) {
            if (stopFn && stopFn(this.startContainer)) {
              break;
            }
            if (isBody(this.startContainer)) {
              break;
            }
            this.setStartBefore(this.startContainer);
          }
          while (
            this.endOffset ==
            (this.endContainer.nodeType == 1
              ? this.endContainer.childNodes.length
              : this.endContainer.nodeValue.length)
          ) {
            if (stopFn && stopFn(this.endContainer)) {
              break;
            }
            if (isBody(this.endContainer)) {
              break;
            }
            this.setEndAfter(this.endContainer);
          }
        }
        return this;
      },
      enlargeToBlockElm: function(ignoreEnd) {
        while (!domUtils.isBlockElm(this.startContainer)) {
          this.setStartBefore(this.startContainer);
        }
        if (!ignoreEnd) {
          while (!domUtils.isBlockElm(this.endContainer)) {
            this.setEndAfter(this.endContainer);
          }
        }
        return this;
      },
      /**
           * 调整Range的边界，使其"缩小"到最合适的位置
           * @method adjustmentBoundary
           * @return { UE.dom.Range } 当前range对象
           * @see UE.dom.Range:shrinkBoundary()
           */
      adjustmentBoundary: function() {
        if (!this.collapsed) {
          while (
            !domUtils.isBody(this.startContainer) &&
            this.startOffset ==
              this.startContainer[
                this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
              ].length &&
            this.startContainer[
              this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
            ].length
          ) {
            this.setStartAfter(this.startContainer);
          }
          while (
            !domUtils.isBody(this.endContainer) &&
            !this.endOffset &&
            this.endContainer[
              this.endContainer.nodeType == 3 ? "nodeValue" : "childNodes"
            ].length
          ) {
            this.setEndBefore(this.endContainer);
          }
        }
        return this;
      },
  
      /**
           * 给range选区中的内容添加给定的inline标签
           * @method applyInlineStyle
           * @param { String } tagName 需要添加的标签名
           * @example
           * ```html
           * <p>xxxx[xxxx]x</p>  ==>  range.applyInlineStyle("strong")  ==>  <p>xxxx[<strong>xxxx</strong>]x</p>
           * ```
           */
  
      /**
           * 给range选区中的内容添加给定的inline标签， 并且为标签附加上一些初始化属性。
           * @method applyInlineStyle
           * @param { String } tagName 需要添加的标签名
           * @param { Object } attrs 跟随新添加的标签的属性
           * @return { UE.dom.Range } 当前选区
           * @example
           * ```html
           * <p>xxxx[xxxx]x</p>
           *
           * ==>
           *
           * <!-- 执行操作 -->
           * range.applyInlineStyle("strong",{"style":"font-size:12px"})
           *
           * ==>
           *
           * <p>xxxx[<strong style="font-size:12px">xxxx</strong>]x</p>
           * ```
           */
      applyInlineStyle: function(tagName, attrs, list) {
        if (this.collapsed) return this;
        this.trimBoundary()
          .enlarge(false, function(node) {
            return node.nodeType == 1 && domUtils.isBlockElm(node);
          })
          .adjustmentBoundary();
        var bookmark = this.createBookmark(),
          end = bookmark.end,
          filterFn = function(node) {
            return node.nodeType == 1
              ? node.tagName.toLowerCase() != "br"
              : !domUtils.isWhitespace(node);
          },
          current = domUtils.getNextDomNode(bookmark.start, false, filterFn),
          node,
          pre,
          range = this.cloneRange();
        while (
          current &&
          domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING
        ) {
          if (current.nodeType == 3 || dtd[tagName][current.tagName]) {
            range.setStartBefore(current);
            node = current;
            while (
              node &&
              (node.nodeType == 3 || dtd[tagName][node.tagName]) &&
              node !== end
            ) {
              pre = node;
              node = domUtils.getNextDomNode(
                node,
                node.nodeType == 1,
                null,
                function(parent) {
                  return dtd[tagName][parent.tagName];
                }
              );
            }
            var frag = range.setEndAfter(pre).extractContents(),
              elm;
            if (list && list.length > 0) {
              var level, top;
              top = level = list[0].cloneNode(false);
              for (var i = 1, ci; (ci = list[i++]); ) {
                level.appendChild(ci.cloneNode(false));
                level = level.firstChild;
              }
              elm = level;
            } else {
              elm = range.document.createElement(tagName);
            }
            if (attrs) {
              domUtils.setAttributes(elm, attrs);
            }
            elm.appendChild(frag);
            //针对嵌套span的全局样式指定，做容错处理
            if (elm.tagName == "SPAN" && attrs && attrs.style) {
              utils.each(elm.getElementsByTagName("span"), function(s) {
                s.style.cssText = s.style.cssText + ";" + attrs.style;
              });
            }
            range.insertNode(list ? top : elm);
            //处理下滑线在a上的情况
            var aNode;
            if (
              tagName == "span" &&
              attrs.style &&
              /text\-decoration/.test(attrs.style) &&
              (aNode = domUtils.findParentByTagName(elm, "a", true))
            ) {
              domUtils.setAttributes(aNode, attrs);
              domUtils.remove(elm, true);
              elm = aNode;
            } else {
              domUtils.mergeSibling(elm);
              domUtils.clearEmptySibling(elm);
            }
            //去除子节点相同的
            domUtils.mergeChild(elm, attrs);
            current = domUtils.getNextDomNode(elm, false, filterFn);
            domUtils.mergeToParent(elm);
            if (node === end) {
              break;
            }
          } else {
            current = domUtils.getNextDomNode(current, true, filterFn);
          }
        }
        return this.moveToBookmark(bookmark);
      },
  
      /**
           * 移除当前选区内指定的inline标签，但保留其中的内容
           * @method removeInlineStyle
           * @param { String } tagName 需要移除的标签名
           * @return { UE.dom.Range } 当前的range对象
           * @example
           * ```html
           * xx[x<span>xxx<em>yyy</em>zz]z</span>  => range.removeInlineStyle(["em"])  => xx[x<span>xxxyyyzz]z</span>
           * ```
           */
  
      /**
           * 移除当前选区内指定的一组inline标签，但保留其中的内容
           * @method removeInlineStyle
           * @param { Array } tagNameArr 需要移除的标签名的数组
           * @return { UE.dom.Range } 当前的range对象
           * @see UE.dom.Range:removeInlineStyle(String)
           */
      removeInlineStyle: function(tagNames) {
        if (this.collapsed) return this;
        tagNames = utils.isArray(tagNames) ? tagNames : [tagNames];
        this.shrinkBoundary().adjustmentBoundary();
        var start = this.startContainer,
          end = this.endContainer;
        while (1) {
          if (start.nodeType == 1) {
            if (utils.indexOf(tagNames, start.tagName.toLowerCase()) > -1) {
              break;
            }
            if (start.tagName.toLowerCase() == "body") {
              start = null;
              break;
            }
          }
          start = start.parentNode;
        }
        while (1) {
          if (end.nodeType == 1) {
            if (utils.indexOf(tagNames, end.tagName.toLowerCase()) > -1) {
              break;
            }
            if (end.tagName.toLowerCase() == "body") {
              end = null;
              break;
            }
          }
          end = end.parentNode;
        }
        var bookmark = this.createBookmark(),
          frag,
          tmpRange;
        if (start) {
          tmpRange = this.cloneRange()
            .setEndBefore(bookmark.start)
            .setStartBefore(start);
          frag = tmpRange.extractContents();
          tmpRange.insertNode(frag);
          domUtils.clearEmptySibling(start, true);
          start.parentNode.insertBefore(bookmark.start, start);
        }
        if (end) {
          tmpRange = this.cloneRange()
            .setStartAfter(bookmark.end)
            .setEndAfter(end);
          frag = tmpRange.extractContents();
          tmpRange.insertNode(frag);
          domUtils.clearEmptySibling(end, false, true);
          end.parentNode.insertBefore(bookmark.end, end.nextSibling);
        }
        var current = domUtils.getNextDomNode(bookmark.start, false, function(
          node
        ) {
          return node.nodeType == 1;
        }),
          next;
        while (current && current !== bookmark.end) {
          next = domUtils.getNextDomNode(current, true, function(node) {
            return node.nodeType == 1;
          });
          if (utils.indexOf(tagNames, current.tagName.toLowerCase()) > -1) {
            domUtils.remove(current, true);
          }
          current = next;
        }
        return this.moveToBookmark(bookmark);
      },
  
      /**
           * 获取当前选中的自闭合的节点
           * @method  getClosedNode
           * @return { Node | NULL } 如果当前选中的是自闭合节点， 则返回该节点， 否则返回NULL
           */
      getClosedNode: function() {
        var node;
        if (!this.collapsed) {
          var range = this.cloneRange().adjustmentBoundary().shrinkBoundary();
          if (selectOneNode(range)) {
            var child = range.startContainer.childNodes[range.startOffset];
            if (
              child &&
              child.nodeType == 1 &&
              (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName])
            ) {
              node = child;
            }
          }
        }
        return node;
      },
  
      /**
           * 在页面上高亮range所表示的选区
           * @method select
           * @return { UE.dom.Range } 返回当前Range对象
           */
      //这里不区分ie9以上，trace:3824
      select: browser.ie
        ? function(noFillData, textRange) {
            var nativeRange;
            if (!this.collapsed) this.shrinkBoundary();
            var node = this.getClosedNode();
            if (node && !textRange) {
              try {
                nativeRange = this.document.body.createControlRange();
                nativeRange.addElement(node);
                nativeRange.select();
              } catch (e) {}
              return this;
            }
            var bookmark = this.createBookmark(),
              start = bookmark.start,
              end;
            nativeRange = this.document.body.createTextRange();
            nativeRange.moveToElementText(start);
            nativeRange.moveStart("character", 1);
            if (!this.collapsed) {
              var nativeRangeEnd = this.document.body.createTextRange();
              end = bookmark.end;
              nativeRangeEnd.moveToElementText(end);
              nativeRange.setEndPoint("EndToEnd", nativeRangeEnd);
            } else {
              if (!noFillData && this.startContainer.nodeType != 3) {
                //使用<span>|x<span>固定住光标
                var tmpText = this.document.createTextNode(fillChar),
                  tmp = this.document.createElement("span");
                tmp.appendChild(this.document.createTextNode(fillChar));
                start.parentNode.insertBefore(tmp, start);
                start.parentNode.insertBefore(tmpText, start);
                //当点b,i,u时，不能清除i上边的b
                removeFillData(this.document, tmpText);
                fillData = tmpText;
                mergeSibling(tmp, "previousSibling");
                mergeSibling(start, "nextSibling");
                nativeRange.moveStart("character", -1);
                nativeRange.collapse(true);
              }
            }
            this.moveToBookmark(bookmark);
            tmp && domUtils.remove(tmp);
            //IE在隐藏状态下不支持range操作，catch一下
            try {
              nativeRange.select();
            } catch (e) {}
            return this;
          }
        : function(notInsertFillData) {
            function checkOffset(rng) {
              function check(node, offset, dir) {
                if (node.nodeType == 3 && node.nodeValue.length < offset) {
                  rng[dir + "Offset"] = node.nodeValue.length;
                }
              }
              check(rng.startContainer, rng.startOffset, "start");
              check(rng.endContainer, rng.endOffset, "end");
            }
            var win = domUtils.getWindow(this.document),
              sel = win.getSelection(),
              txtNode;
            //FF下关闭自动长高时滚动条在关闭dialog时会跳
            //ff下如果不body.focus将不能定位闭合光标到编辑器内
            browser.gecko ? this.document.body.focus() : win.focus();
            if (sel) {
              sel.removeAllRanges();
              // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断
              // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'
              if (this.collapsed && !notInsertFillData) {
                //                    //opear如果没有节点接着，原生的不能够定位,不能在body的第一级插入空白节点
                //                    if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {
                //                        var tmp = this.document.createTextNode('');
                //                        this.insertNode(tmp).setStart(tmp, 0).collapse(true);
                //                    }
                //
                //处理光标落在文本节点的情况
                //处理以下的情况
                //<b>|xxxx</b>
                //<b>xxxx</b>|xxxx
                //xxxx<b>|</b>
                var start = this.startContainer,
                  child = start;
                if (start.nodeType == 1) {
                  child = start.childNodes[this.startOffset];
                }
                if (
                  !(start.nodeType == 3 && this.startOffset) &&
                  (child
                    ? !child.previousSibling ||
                        child.previousSibling.nodeType != 3
                    : !start.lastChild || start.lastChild.nodeType != 3)
                ) {
                  txtNode = this.document.createTextNode(fillChar);
                  //跟着前边走
                  this.insertNode(txtNode);
                  removeFillData(this.document, txtNode);
                  mergeSibling(txtNode, "previousSibling");
                  mergeSibling(txtNode, "nextSibling");
                  fillData = txtNode;
                  this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);
                }
              }
              var nativeRange = this.document.createRange();
              if (
                this.collapsed &&
                browser.opera &&
                this.startContainer.nodeType == 1
              ) {
                var child = this.startContainer.childNodes[this.startOffset];
                if (!child) {
                  //往前靠拢
                  child = this.startContainer.lastChild;
                  if (child && domUtils.isBr(child)) {
                    this.setStartBefore(child).collapse(true);
                  }
                } else {
                  //向后靠拢
                  while (child && domUtils.isBlockElm(child)) {
                    if (child.nodeType == 1 && child.childNodes[0]) {
                      child = child.childNodes[0];
                    } else {
                      break;
                    }
                  }
                  child && this.setStartBefore(child).collapse(true);
                }
              }
              //是createAddress最后一位算的不准，现在这里进行微调
              checkOffset(this);
              nativeRange.setStart(this.startContainer, this.startOffset);
              nativeRange.setEnd(this.endContainer, this.endOffset);
              sel.addRange(nativeRange);
            }
            return this;
          },
  
      /**
           * 滚动到当前range开始的位置
           * @method scrollToView
           * @param { Window } win 当前range对象所属的window对象
           * @return { UE.dom.Range } 当前Range对象
           */
  
      /**
           * 滚动到距离当前range开始位置 offset 的位置处
           * @method scrollToView
           * @param { Window } win 当前range对象所属的window对象
           * @param { Number } offset 距离range开始位置处的偏移量， 如果为正数， 则向下偏移， 反之， 则向上偏移
           * @return { UE.dom.Range } 当前Range对象
           */
      scrollToView: function(win, offset) {
        win = win ? window : domUtils.getWindow(this.document);
        var me = this,
          span = me.document.createElement("span");
        //trace:717
        span.innerHTML = "&nbsp;";
        me.cloneRange().insertNode(span);
        domUtils.scrollToView(span, win, offset);
        domUtils.remove(span);
        return me;
      },
  
      /**
           * 判断当前选区内容是否占位符
           * @private
           * @method inFillChar
           * @return { Boolean } 如果是占位符返回true，否则返回false
           */
      inFillChar: function() {
        var start = this.startContainer;
        if (
          this.collapsed &&
          start.nodeType == 3 &&
          start.nodeValue.replace(new RegExp("^" + domUtils.fillChar), "")
            .length +
            1 ==
            start.nodeValue.length
        ) {
          return true;
        }
        return false;
      },
  
      /**
           * 保存
           * @method createAddress
           * @private
           * @return { Boolean } 返回开始和结束的位置
           * @example
           * ```html
           * <body>
           *     <p>
           *         aaaa
           *         <em>
           *             <!-- 选区开始 -->
           *             bbbb
           *             <!-- 选区结束 -->
           *         </em>
           *     </p>
           *
           *     <script>
           *         //output: {startAddress:[0,1,0,0],endAddress:[0,1,0,4]}
           *         console.log( range.createAddress() );
           *     </script>
           * </body>
           * ```
           */
      createAddress: function(ignoreEnd, ignoreTxt) {
        var addr = {},
          me = this;
  
        function getAddress(isStart) {
          var node = isStart ? me.startContainer : me.endContainer;
          var parents = domUtils.findParents(node, true, function(node) {
            return !domUtils.isBody(node);
          }),
            addrs = [];
          for (var i = 0, ci; (ci = parents[i++]); ) {
            addrs.push(domUtils.getNodeIndex(ci, ignoreTxt));
          }
          var firstIndex = 0;
  
          if (ignoreTxt) {
            if (node.nodeType == 3) {
              var tmpNode = node.previousSibling;
              while (tmpNode && tmpNode.nodeType == 3) {
                firstIndex += tmpNode.nodeValue.replace(fillCharReg, "").length;
                tmpNode = tmpNode.previousSibling;
              }
              firstIndex += isStart ? me.startOffset : me.endOffset; // - (fillCharReg.test(node.nodeValue) ? 1 : 0 )
            } else {
              node = node.childNodes[isStart ? me.startOffset : me.endOffset];
              if (node) {
                firstIndex = domUtils.getNodeIndex(node, ignoreTxt);
              } else {
                node = isStart ? me.startContainer : me.endContainer;
                var first = node.firstChild;
                while (first) {
                  if (domUtils.isFillChar(first)) {
                    first = first.nextSibling;
                    continue;
                  }
                  firstIndex++;
                  if (first.nodeType == 3) {
                    while (first && first.nodeType == 3) {
                      first = first.nextSibling;
                    }
                  } else {
                    first = first.nextSibling;
                  }
                }
              }
            }
          } else {
            firstIndex = isStart
              ? domUtils.isFillChar(node) ? 0 : me.startOffset
              : me.endOffset;
          }
          if (firstIndex < 0) {
            firstIndex = 0;
          }
          addrs.push(firstIndex);
          return addrs;
        }
        addr.startAddress = getAddress(true);
        if (!ignoreEnd) {
          addr.endAddress = me.collapsed
            ? [].concat(addr.startAddress)
            : getAddress();
        }
        return addr;
      },
  
      /**
           * 保存
           * @method createAddress
           * @private
           * @return { Boolean } 返回开始和结束的位置
           * @example
           * ```html
           * <body>
           *     <p>
           *         aaaa
           *         <em>
           *             <!-- 选区开始 -->
           *             bbbb
           *             <!-- 选区结束 -->
           *         </em>
           *     </p>
           *
           *     <script>
           *         var range = editor.selection.getRange();
           *         range.moveToAddress({startAddress:[0,1,0,0],endAddress:[0,1,0,4]});
           *         range.select();
           *         //output: 'bbbb'
           *         console.log(editor.selection.getText());
           *     </script>
           * </body>
           * ```
           */
      moveToAddress: function(addr, ignoreEnd) {
        var me = this;
        function getNode(address, isStart) {
          var tmpNode = me.document.body,
            parentNode,
            offset;
          for (var i = 0, ci, l = address.length; i < l; i++) {
            ci = address[i];
            parentNode = tmpNode;
            tmpNode = tmpNode.childNodes[ci];
            if (!tmpNode) {
              offset = ci;
              break;
            }
          }
          if (isStart) {
            if (tmpNode) {
              me.setStartBefore(tmpNode);
            } else {
              me.setStart(parentNode, offset);
            }
          } else {
            if (tmpNode) {
              me.setEndBefore(tmpNode);
            } else {
              me.setEnd(parentNode, offset);
            }
          }
        }
        getNode(addr.startAddress, true);
        !ignoreEnd && addr.endAddress && getNode(addr.endAddress);
        return me;
      },
  
      /**
           * 判断给定的Range对象是否和当前Range对象表示的是同一个选区
           * @method equals
           * @param { UE.dom.Range } 需要判断的Range对象
           * @return { Boolean } 如果给定的Range对象与当前Range对象表示的是同一个选区， 则返回true， 否则返回false
           */
      equals: function(rng) {
        for (var p in this) {
          if (this.hasOwnProperty(p)) {
            if (this[p] !== rng[p]) return false;
          }
        }
        return true;
      },
  
      /**
           * 遍历range内的节点。每当遍历一个节点时， 都会执行参数项 doFn 指定的函数， 该函数的接受当前遍历的节点
           * 作为其参数。
           * @method traversal
           * @param { Function }  doFn 对每个遍历的节点要执行的方法， 该方法接受当前遍历的节点作为其参数
           * @return { UE.dom.Range } 当前range对象
           * @example
           * ```html
           *
           * <body>
           *
           *     <!-- 选区开始 -->
           *     <span></span>
           *     <a></a>
           *     <!-- 选区结束 -->
           * </body>
           *
           * <script>
           *
           *     //output: <span></span><a></a>
           *     console.log( range.cloneContents() );
           *
           *     range.traversal( function ( node ) {
           *
           *         if ( node.nodeType === 1 ) {
           *             node.className = "test";
           *         }
           *
           *     } );
           *
           *     //output: <span class="test"></span><a class="test"></a>
           *     console.log( range.cloneContents() );
           *
           * </script>
           * ```
           */
  
      /**
           * 遍历range内的节点。
           * 每当遍历一个节点时， 都会执行参数项 doFn 指定的函数， 该函数的接受当前遍历的节点
           * 作为其参数。
           * 可以通过参数项 filterFn 来指定一个过滤器， 只有符合该过滤器过滤规则的节点才会触
           * 发doFn函数的执行
           * @method traversal
           * @param { Function } doFn 对每个遍历的节点要执行的方法， 该方法接受当前遍历的节点作为其参数
           * @param { Function } filterFn 过滤器， 该函数接受当前遍历的节点作为参数， 如果该节点满足过滤
           *                      规则， 请返回true， 该节点会触发doFn， 否则， 请返回false， 则该节点不
           *                      会触发doFn。
           * @return { UE.dom.Range } 当前range对象
           * @see UE.dom.Range:traversal(Function)
           * @example
           * ```html
           *
           * <body>
           *
           *     <!-- 选区开始 -->
           *     <span></span>
           *     <a></a>
           *     <!-- 选区结束 -->
           * </body>
           *
           * <script>
           *
           *     //output: <span></span><a></a>
           *     console.log( range.cloneContents() );
           *
           *     range.traversal( function ( node ) {
           *
           *         node.className = "test";
           *
           *     }, function ( node ) {
           *          return node.nodeType === 1;
           *     } );
           *
           *     //output: <span class="test"></span><a class="test"></a>
           *     console.log( range.cloneContents() );
           *
           * </script>
           * ```
           */
      traversal: function(doFn, filterFn) {
        if (this.collapsed) return this;
        var bookmark = this.createBookmark(),
          end = bookmark.end,
          current = domUtils.getNextDomNode(bookmark.start, false, filterFn);
        while (
          current &&
          current !== end &&
          domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING
        ) {
          var tmpNode = domUtils.getNextDomNode(current, false, filterFn);
          doFn(current);
          current = tmpNode;
        }
        return this.moveToBookmark(bookmark);
      }
    };
  })();
  