模拟实现 Range 的 insertNode() 方法

作者:怿飞 来源:怿飞blog 时间:2010-11-30 21:39:00 

最近对 RangeSelection 比较感兴趣。

基本非 IE 的浏览器都支持 DOM Level2 中的 Range,而 IE 中仅有自己的简单处理方法(Text Rang)。

扩展阅读:

而 IE 下的 Text Rang 主要用来处理文本,并非 DOM 节点,那如何在 IE 下模拟 DOM Level2 中的 Range 呢?

根据规范的 API,我们需要模拟下述属性和方法:

function zRange() {
    // Inital states
    this.startContainer = document;
    this.startOffset = 0;
    this.endContainer = document;
    this.endOffset = 0;
    this.commonAncestorContainer = document;
    this.collapsed = true;

    // Range constants
    this.START_TO_START = 0;
    this.START_TO_END = 1;
    this.END_TO_END = 2;
    this.END_TO_START = 3;
}

zRange.prototype = {
    // Public methods
    setStart : function(node, offset){},
    setEnd : function(node, offset){},
    setStartBefore : function(node){},
    setStartAfter : function(node){},
    setEndBefore : function(node){},
    setEndAfter : function(node){},
    collapse : function(toStart) {},
    selectNode : function(node) {},
    selectNodeContents : function(node){},
    deleteContents : function() {},
    extractContents : function(){},
    cloneContents : function() {},
    surroundContents : function () {},
    insertNode : function(node) {},
    cloneRange : function() {},
    detach : function() {},
    compareBoundaryPoints : function (how, sourceRange) {},
    constructor : zRange
}

我们还可以看一组使用 Range 方法和属性的统计数据,对于 2/8 原则的实践或许有帮助:

/**
 * Resource reference : http://kb.operachina.com/node/147
 *
 * collapse             51,435
 * setStartBefore       43,138
 * setStartAfter        40,270
 * selectNodeContents   37,027
 * collapsed            12,862
 * selectNode            4,636
 * deleteContents        3,935
 * setStart              3,171
 * startOffset           3,150
 * setEnd                3,086
 * detach                2,732
 * startContainer        2,659
 * endOffset             2,647
 * insertNode            2,321
 * cloneContents         2,261
 * endContainer          2,236
 * cloneRange            1,993
 * setEndAfter           1,911
 *
 */

下面我们简单讨论一下 Range 的 insertNode() 方法的模拟实现:

The insertNode() method inserts the specified node into the Range’s context tree. The node is inserted at the start boundary-point of the Range, without modifying it.

If the start boundary point of the Range is in a Text node, the insertNode operation splits the Text node at the boundary point. If the node to be inserted is also a Text node, the resulting adjacent Text nodes are not normalized automatically; this operation is left to the application.

The Node passed into this method can be a DocumentFragment. In that case, the contents of the DocumentFragment are inserted at the start boundary-point of the Range, but the DocumentFragment itself is not. Note that if the Node represents the root of a sub-tree, the entire sub-tree is inserted.

从上面的引用得知 insertNode() 方法用来在选区的开头插入节点,固我们先获取 Range 对象的 startContainer(Range 是从那个节点中开始的,即选区中第一个节点的父节点) 和 startOffset(在 startContainer 中 Range 开始的偏移位置) 属性。

var sc = this.startContainer,
    so = this.startOffset;

注: 如果 startContainer 是文本节点、注释节点或者是 CData 节点,startOffset 是指 Range 开始前的字符数,否则,偏移是 Range 中的第一个节点在其父节点中的索引。

接下来,我们需将情况分为下面两种:

第一种情形:startContainer 节点为文本节点、注释节点或者是 CData 节点。“startOffset 是指 Range 开始前的字符数”。

  • 如果 startOffset 等于 0,则表示 Range 是从 startContainer 起始位置开始,应将 node 插入到 startContainer 节点之前 sc.parentNode.insertBefore(node, sc);

  • 如果 startOffset 大于等于 startContainer 本身的节点长度,则表示 Range 是从 startContainer 末尾位置开始,应将 node 插入到 startContainer 节点之后,即如果存在下一节点,则插入到下一之前 sc.parentNode.insertBefore(node, sc.nextSibling);,如果不存在下一节点,则加入到 startContainer父节点最后 sc.parentNode.appendChild(node);

  • 如果 startOffset 在 startContainer 本身的节点长度之内,我们通过oSplitNode = object.splitText( [iIndex])将节点在startOffset 一分为二 nn = sc.splitText(so);,分为两个节点,则应将 node 插入到新生成的第二个节点之前 sc.parentNode.insertBefore(node, nn);

    The text node that invokes the splitText method has a nodeValue equal to the substring of the value, from zero to iIndex. The new text node has a nodeValue of the substring of the original value, from the specified index to the value length. Text node integrity is not preserved when the document is saved or persisted.

if (so===0) {
    // At the start of text
    sc.parentNode.insertBefore(node, sc);
} else if (so >= sc.nodeValue.length) {
    // At the end of text
    if (ns) {
        sc.parentNode.insertBefore(node, sc.nextSibling);
    } else {
        sc.parentNode.appendChild(node);
    }
} else {
    // Middle, need to split
    // http://msdn.microsoft.com/zh-cn/library/ms536764.aspx
    nn = sc.splitText(so);
    sc.parentNode.insertBefore(node, nn);
}

第二种情形:startContainer 节点为非 第一种情形中的节点。“偏移是 Range 中的第一个节点在其父节点中的索引”。获取 startContainer 中子节点偏移量为 startOffset 的节点 cn = sc.childNodes[so];,如果存在,则按照 insertNode 方法的定义,应将 node 插入到该节点之前 sc.insertBefore(node, cn);,如果不存在,即 startOffset 大于等于 sc.childNodes.length,则应将 node 插入到 startContainer 的子节点最后sc.appendChild(node);

if (sc.childNodes.length > 0) {
    cn = sc.childNodes[so];
}

if (cn) {
    sc.insertBefore(node, cn);
} else {
    sc.appendChild(node);
}

详细代码实现如下:

zRange.prototype.insertNode = function(node) {
    var sc = this.startContainer,
        so = this.startOffset,
        p = sc.parentNode,
        ns = sc.nextSibling,
        nn, cn;

    // 如果节点是 TEXT_NODE 或者 CDATA_SECTION_NODE
    if ((sc.nodeType === 3 || sc.nodeType === 4 || sc.nodeType === 8 ) && sc.nodeValue) {
        if (so === 0) {
            // At the start of text
            p.insertBefore(node, sc);
        } else if (so >= sc.nodeValue.length) {
            // At the end of text
            if (ns) {
                p.insertBefore(node, ns);
            } else {
                p.appendChild(node);
            }
        } else {
            // Middle, need to split
            // http://msdn.microsoft.com/zh-cn/library/ms536764.aspx
            nn = sc.splitText(so);
            p.insertBefore(node, nn);
        }
    } else {
        if (sc.childNodes.length > 0) {
            cn = sc.childNodes[so];
        }

        if (cn) {
            sc.insertBefore(node, cn);
        } else {
            sc.appendChild(node);
        }
    }
}

剩下的方法,大家可以尝试着去模拟一把,其实并不复杂,也许会其乐无穷,呵呵

标签:range,insertnode
0
投稿

猜你喜欢

  • 解决json中ensure_ascii=False的问题

    2023-01-04 10:49:11
  • python中的随机函数小结

    2021-07-01 04:26:59
  • go 下载非标准库包(部份包被墙了)到本地使用的方法

    2024-05-10 13:56:50
  • Python格式化输出--%s,%d,%f的代码解析

    2022-10-31 06:26:44
  • Python 实现递归法解决迷宫问题的示例代码

    2021-01-31 08:14:23
  • mysql创建外键报错的原因及解决(can't not create table)

    2024-01-15 11:57:44
  • python实现网页自动签到功能

    2024-01-03 00:13:14
  • MySQL数据库优化详解

    2024-01-23 12:51:55
  • Python OrderedDict的使用案例解析

    2021-11-20 22:47:25
  • JavaScript验证图片类型(扩展名)的函数分享

    2024-04-17 09:51:00
  • Python keras.metrics源代码分析

    2023-10-28 20:58:14
  • SQL联合查询inner join、outer join和cross join的区别详解

    2024-01-14 21:33:41
  • Python中import导入不同目录的模块方法详解

    2021-04-08 02:37:08
  • pymongo insert_many 批量插入的实例

    2023-05-30 04:27:43
  • 基于Python实现简单学生管理系统

    2021-01-02 18:58:11
  • python编写WAF与Sqlmap结合实现指纹探测

    2022-05-23 08:10:17
  • Python中的FTP通信模块ftplib的用法整理

    2021-12-31 19:35:45
  • Python3.5基础之变量、数据结构、条件和循环语句、break与continue语句实例详解

    2023-03-13 09:28:44
  • Python实现上下文管理器的方法

    2021-06-22 17:31:15
  • opencv+python识别七段数码显示器的数字(数字识别)

    2022-03-03 00:01:51
  • asp之家 网络编程 m.aspxhome.com