Javascript 用JS/jQuery中的文本替换DOM树中的非图像元素

Javascript 用JS/jQuery中的文本替换DOM树中的非图像元素,javascript,jquery,dom,contenteditable,Javascript,Jquery,Dom,Contenteditable,我想用文本替换contenteditable中的非img元素。但是,我想保留任何img元素,包括嵌套在其他元素中的元素。换言之: 给定输入,例如: <div><span>Foo <strong>Bar <img src="blah.png"></strong> and more text <img src="another.png"></span> With some other text <img src

我想用文本替换
contenteditable
中的非
img
元素。但是,我想保留任何
img
元素,包括嵌套在其他元素中的元素。换言之:

给定输入,例如:

<div><span>Foo <strong>Bar <img src="blah.png"></strong> and more text <img src="another.png"></span> With some other text <img src="yetmore.png"></div>
Foo和更多包含其他文本的文本
我想提出:

Foo Bar <img src="blah.png"> and more text <img src="another.png"> With some other text <img src="yetmore.png">
Foo-Bar和更多带有其他文本的文本
由于这是一个
contenteditable
,我不想使用
innerHTML
读/写,因为这样会丢失光标位置等(恢复它很困难,因为最终会使用不同的DOM树,所以选择节点会丢失)

我的最佳选择是在树上迭代并手动拆分和连接文本节点等等吗?我希望有一个更好的方法,或者一个图书馆可以做这样的事情

$(document).ready(function(){
    replaceChildren($('#contenteditable'));
});

function replaceChildren(elem){
    $(elem).children("*").not("img").each(function(){
        if ($(this).children("*").not("img").length>0){
            replaceChildren(this);
        }
        $(this).after($(this).html());
        $(this).remove();
    });
}
某种递归解


某种递归解决方案。

对根元素进行浅克隆。沿着原始元素树走下去,收集文本。当您遇到img元素时,将收集的文本添加到作为克隆子节点的文本节点。附加img。再次开始收集文本

继续,直到结束,然后用克隆替换文档中的原始根元素

编辑 比如:

function toArray(o) {
  var a = [], i = o.length;
  while (i--) {
    a[i] = o[i];
  }
  return a;
}

function cleanUp(el) {

  var e = el.cloneNode(false);

  function addText(text) {
    if (text != '') {
      e.appendChild(document.createTextNode(text));
    }
  }

  function collectText(el) {
    var node, nodes = toArray(el.childNodes);
    var text = '';

    for (var i=0, iLen=nodes.length; i<iLen; i++) {
      node = nodes[i];

      if (node.tagName && node.tagName.toLowerCase() == 'img') {
        addText(text);
        e.appendChild(node);
        text = '';

      } else if (node.nodeType == 3) {
        text += node.data;

      } else if (node.nodeType == 1) {
        addText(text);
        text = '';
        collectText(node);
      }
    }

    if (text != '') {
      e.appendChild(document.createTextNode(text));
    }
  }
  collectText(el);
  el.parentNode.replaceChild(e, el);
}
函数到阵列(o){
变量a=[],i=o.length;
而(我--){
a[i]=o[i];
}
返回a;
}
函数清理(el){
变量e=el.cloneNode(假);
函数addText(文本){
如果(文本!=''){
e、 appendChild(document.createTextNode(text));
}
}
函数集合文本(el){
变量节点,节点=toArray(el.childNodes);
var text='';

对于(var i=0,iLen=nodes.length;i创建根元素的浅克隆。沿着原始元素树走,收集文本。当遇到img元素时,将收集的文本添加到作为克隆子元素的文本节点。追加img。再次开始收集文本

继续,直到结束,然后用克隆替换文档中的原始根元素

编辑 比如:

function toArray(o) {
  var a = [], i = o.length;
  while (i--) {
    a[i] = o[i];
  }
  return a;
}

function cleanUp(el) {

  var e = el.cloneNode(false);

  function addText(text) {
    if (text != '') {
      e.appendChild(document.createTextNode(text));
    }
  }

  function collectText(el) {
    var node, nodes = toArray(el.childNodes);
    var text = '';

    for (var i=0, iLen=nodes.length; i<iLen; i++) {
      node = nodes[i];

      if (node.tagName && node.tagName.toLowerCase() == 'img') {
        addText(text);
        e.appendChild(node);
        text = '';

      } else if (node.nodeType == 3) {
        text += node.data;

      } else if (node.nodeType == 1) {
        addText(text);
        text = '';
        collectText(node);
      }
    }

    if (text != '') {
      e.appendChild(document.createTextNode(text));
    }
  }
  collectText(el);
  el.parentNode.replaceChild(e, el);
}
函数到阵列(o){
变量a=[],i=o.length;
而(我--){
a[i]=o[i];
}
返回a;
}
函数清理(el){
变量e=el.cloneNode(假);
函数addText(文本){
如果(文本!=''){
e、 appendChild(document.createTextNode(text));
}
}
函数集合文本(el){
变量节点,节点=toArray(el.childNodes);
var text='';

for(var i=0,iLen=nodes.length;i执行DOM替换几乎肯定也会丢失光标位置/选择,但仍然是正确的方法。我建议使用for和跨浏览器范围/选择处理(披露:我是Rangy的作者)

下面是一个示例,它删除了非
元素,并在所有主要浏览器(包括IE 6)中保留了以前的选择/插入符号位置。它递归地将主容器节点的
和文本子体移动到
DocumentFragment
中,并在最后将片段附加到现在为空的容器节点之前删除所有其他节点。它还规范化(即连接相邻的文本节点)

带Rangy选择的JSFIDLE保存和恢复:

元件移除代码:

function removeNonImgElements(node) {
    var frag = document.createDocumentFragment();

    function move(node, moveSelf) {
        var type = node.nodeType, name = node.nodeName;

        // Deal with child nodes first
        var child;
        while ( (child = node.firstChild) ) {
            move(child, true);
        }

        if (!moveSelf) {
            return;
        }

        // Keep text, images and Rangy selection marker elements
        if (type == 1 && (name == "IMG" ||
                 (name == "SPAN" && /^selectionBoundary/.test(node.id)))) {
            frag.appendChild(node);
        } else if (type == 3) {
            var previousNode = frag.lastChild;
            if (previousNode && previousNode.nodeType == 3) {
                // Concatenate text nodes rather than have two adjacent
                previousNode.data = previousNode.data + node.data;
                node.parentNode.removeChild(node);
            } else {
                frag.appendChild(node);
            }
        } else {
            node.parentNode.removeChild(node);
        }
    }

    move(node, false);
    node.appendChild(frag);
}

进行DOM替换几乎肯定也会丢失光标位置/选择,但仍然是正确的方法。我建议使用,以及跨浏览器范围/选择处理(披露:我是Rangy的作者)

下面是一个示例,它删除了非
元素,并在所有主要浏览器(包括IE 6)中保留了以前的选择/插入符号位置。它递归地将主容器节点的
和文本子体移动到
DocumentFragment
中,并在最后将片段附加到现在为空的容器节点之前删除所有其他节点。它还规范化(即连接相邻的文本节点)

带Rangy选择的JSFIDLE保存和恢复:

元件移除代码:

function removeNonImgElements(node) {
    var frag = document.createDocumentFragment();

    function move(node, moveSelf) {
        var type = node.nodeType, name = node.nodeName;

        // Deal with child nodes first
        var child;
        while ( (child = node.firstChild) ) {
            move(child, true);
        }

        if (!moveSelf) {
            return;
        }

        // Keep text, images and Rangy selection marker elements
        if (type == 1 && (name == "IMG" ||
                 (name == "SPAN" && /^selectionBoundary/.test(node.id)))) {
            frag.appendChild(node);
        } else if (type == 3) {
            var previousNode = frag.lastChild;
            if (previousNode && previousNode.nodeType == 3) {
                // Concatenate text nodes rather than have two adjacent
                previousNode.data = previousNode.data + node.data;
                node.parentNode.removeChild(node);
            } else {
                frag.appendChild(node);
            }
        } else {
            node.parentNode.removeChild(node);
        }
    }

    move(node, false);
    node.appendChild(frag);
}

仍然在
after()调用中使用
innerHTML
。仍然在
after()调用中使用
innerHTML
call。我不会为hasChildNodes测试操心,因为你正在删除节点,你可以在有第一个孩子的时候继续进行。连接相邻的文本节点是个好主意。@RobG:是的,我同意你的意见。我不确定为什么要把它放进去;我通常不会操心。我会删除它。我做了这个,但最终使用了Rob的解决方案,bec因为添加Rangy对我来说有点太重了(不可否认,如果我一直这么说,并最终有数千行代码来破解问题,我可能应该重新评估——但我们还没有达到这一点。在我们的应用程序中,我们正在对粘贴/keydown/mouseup/mousedown进行清理。这意味着焦点应该始终位于被替换内容的最后一个节点之后,我只是通过硬编码一个使用
setStartAfter
窗口进行范围设置。在这样的清理之后,getSelection
似乎在我的测试中效果很好。@Gijs:这很公平。Rangy的主要使用案例是如果您支持IE<9,我假设如果您使用的是范围和
窗口。getSelection
,那么Rangy在上面的函数是最小的,很容易删除,但是@RobG的解决方案也一样好。我不想麻烦hasChildNodes测试,因为你要删除节点,你可以在有第一个孩子的时候继续进行。连接相邻的文本节点是个好主意。@RobG:是的,我同意你的意见。我不确定为什么我会把它放进去;我通常不会麻烦。我将删除它。我+了这个,但最终还是使用了Rob的解决方案,因为添加Rangy对于我正在做的事情来说有点太过繁重了(不可否认,如果我一直这么说,并且最终有数千行代码需要修改的话)