从 contentEditable div 中提取文本

IT技术 javascript jquery html css contenteditable
2021-02-25 16:04:54

我有一个 div 设置为contentEditable" white-space:pre"设置样式,所以它保留了换行符之类的东西。在 Safari、FF 和 IE 中,div 的外观和工作原理几乎相同。一切都很好。我想要做的是从这个 div 中提取文本,但不会丢失格式——特别是换行符。

我们正在使用 jQuery,它的text()功能基本上是做一个预排序的 DFS,并将 DOM 的那个分支中的所有内容粘合在一起成为一个单一的块。这会丢失格式。

我查看了该html()函数,但似乎所有三个浏览器都对在我的contentEditablediv 中在幕后生成的实际 HTML 执行不同的操作假设我在我的 div 中输入:

1
2
3

这些是结果:

野生动物园 4:

1
<div>2</div>
<div>3</div>

火狐 3.6:

1
<br _moz_dirty="">
2
<br _moz_dirty="">
3
<br _moz_dirty="">
<br _moz_dirty="" type="_moz">

IE 8:

<P>1</P><P>2</P><P>3</P>

啊。这里没有什么非常一致的。令人惊讶的是,MSIE 看起来是最理智的!(大写的 P 标签和所有)

div 将动态设置样式(字体、颜色、大小和对齐方式),这是使用 CSS 完成的,所以我不确定是否可以使用pre标签(在我使用 Google 找到的某些页面上提到过)。

有谁知道任何 JavaScript 代码和/或 jQuery 插件或一些可以从 contentEditable div 中提取文本以保留换行符的东西?如果不需要,我宁愿不重新发明解析轮。

更新:我getText从 jQuery 1.4.2 中提取了函数并对其进行了修改,以使用大部分完整的空格提取它(我只在添加换行符的地方更改了一行);

function extractTextWithWhitespace( elems ) {
    var ret = "", elem;

    for ( var i = 0; elems[i]; i++ ) {
        elem = elems[i];

        // Get the text from text nodes and CDATA nodes
        if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
            ret += elem.nodeValue + "\n";

        // Traverse everything else, except comment nodes
        } else if ( elem.nodeType !== 8 ) {
            ret += extractTextWithWhitespace2( elem.childNodes );
        }
    }

    return ret;
}

我调用此函数并使用其输出将其分配给带有 jQ​​uery 的 XML 节点,例如:

var extractedText = extractTextWithWhitespace($(this));
var $someXmlNode = $('<someXmlNode/>');
$someXmlNode.text(extractedText);

生成的 XML 最终通过 AJAX 调用发送到服务器。

这在 Safari 和 Firefox 中运行良好。

在 IE 上,只有第一个 '\n' 似乎以某种方式被保留。进一步研究它,看起来 jQuery 正在像这样设置文本(jQuery-1.4.2.js 的第 4004 行):

return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );

继续阅读createTextNode,似乎 IE 的实现可能会混淆空白。这是真的还是我做错了什么?

6个回答

不幸的是,您仍然必须为pre每个浏览器单独处理此情况(在许多情况下我不容忍浏览器检测,使用功能检测......但在这种情况下这是必要的),但幸运的是您可以处理所有这些非常简洁,像这样:

var ce = $("<pre />").html($("#edit").html());
if($.browser.webkit) 
  ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; });    
if($.browser.msie) 
  ce.find("p").replaceWith(function() { return this.innerHTML  +  "<br>"; });
if($.browser.mozilla || $.browser.opera ||$.browser.msie )
  ce.find("br").replaceWith("\n");

var textWithWhiteSpaceIntact = ce.text();

你可以在这里测试一下IE 特别麻烦,因为它的方式&nbsp;和文本转换中的新行,这就是为什么它得到<br>上面处理以使其一致,所以它需要 2 次才能正确处理。

上面#editcontentEditable组件的ID ,所以把它改掉,或者把它变成一个函数,例如:

function getContentEditableText(id) {
    var ce = $("<pre />").html($("#" + id).html());
    if ($.browser.webkit)
      ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; });
    if ($.browser.msie)
      ce.find("p").replaceWith(function() { return this.innerHTML + "<br>"; });
    if ($.browser.mozilla || $.browser.opera || $.browser.msie)
      ce.find("br").replaceWith("\n");

    return ce.text();
}

你可以在这里测试或者,因为无论如何它都是建立在 jQuery 方法上的,所以把它作为一个插件,像这样:

$.fn.getPreText = function () {
    var ce = $("<pre />").html(this.html());
    if ($.browser.webkit)
      ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; });
    if ($.browser.msie)
      ce.find("p").replaceWith(function() { return this.innerHTML + "<br>"; });
    if ($.browser.mozilla || $.browser.opera || $.browser.msie)
      ce.find("br").replaceWith("\n");

    return ce.text();
};

然后你可以调用它$("#edit").getPreText()你可以在这里测试那个版本

@Tim - 我无法让你在 IE 或 Opera 中工作:jsfiddle.net/UjZEN/3
2021-04-25 16:04:54
这(小提琴上方)在 chrome 中保持平衡...... 1)在单独的行上添加 1,2,3,4 2)测试,看起来不错 3)转到第 2 行的开头,按退格键 4)按 Enter 5)测试- 注意第 2、3、4 行现在都在一行上
2021-04-26 16:04:54
oop。正如您所观察到的,浏览器检测很糟糕。幸运的是,在这里可以避免:请参阅我的答案。
2021-05-14 16:04:54

直到现在,我才忘记了这个问题,当时 Nico 悬赏了它。

我通过编写自己需要的函数解决了这个问题,从现有的 jQuery 代码库中提取一个函数并修改它以根据需要工作。

我已经在 Safari (WebKit)、IE、Firefox 和 Opera 上测试了这个功能。我没有费心检查任何其他浏览器,因为整个 contentEditable 东西都是非标准的。如果任何浏览器的更新实现 contentEditable 的方式发生变化,则任何浏览器的更新也可能会破坏此功能。所以程序员要小心。

function extractTextWithWhitespace(elems)
{
    var lineBreakNodeName = "BR"; // Use <br> as a default
    if ($.browser.webkit)
    {
        lineBreakNodeName = "DIV";
    }
    else if ($.browser.msie)
    {
        lineBreakNodeName = "P";
    }
    else if ($.browser.mozilla)
    {
        lineBreakNodeName = "BR";
    }
    else if ($.browser.opera)
    {
        lineBreakNodeName = "P";
    }
    var extractedText = extractTextWithWhitespaceWorker(elems, lineBreakNodeName);

    return extractedText;
}

// Cribbed from jQuery 1.4.2 (getText) and modified to retain whitespace
function extractTextWithWhitespaceWorker(elems, lineBreakNodeName)
{
    var ret = "";
    var elem;

    for (var i = 0; elems[i]; i++)
    {
        elem = elems[i];

        if (elem.nodeType === 3     // text node
            || elem.nodeType === 4) // CDATA node
        {
            ret += elem.nodeValue;
        }

        if (elem.nodeName === lineBreakNodeName)
        {
            ret += "\n";
        }

        if (elem.nodeType !== 8) // comment node
        {
            ret += extractTextWithWhitespace(elem.childNodes, lineBreakNodeName);
        }
    }

    return ret;
}
这也在 Chrome 中中断 - 1) 在单独的行中输入 1,2,3,4 2) 返回到第 1 行 3) 输入几个单词 4) 转到第二行的开头,按退格键,按回车键,按退格键 5 ) 查看结果,第 2 行后面会有一个额外的换行符
2021-04-25 16:04:54

看到这个小提琴

或者这个帖子

如何解析具有浏览器兼容性的可编辑 DIV 文本

经过努力创造......

我今天在 Firefox 中发现了这一点:

我将一个 contenteditable div 的空白设置为“pre”传递给这个函数,并且它工作得很好。

我添加了一行来显示有多少个节点,以及一个将输出放入另一个 PRE 的按钮,只是为了证明换行符完好无损。

它基本上是这样说的:

For each child node of the DIV,
   if it contains the 'data' property,
      add the data value to the output
   otherwise
      add an LF (or a CRLF for Windows)
}
and return the result.

有个问题,呵呵。当您在原始文本的任何行的末尾按 Enter 键时,不是放入 LF,而是放入“”。您可以再次按 Enter 键,它会在其中放入 LF,但不是第一次。并且您必须删除“”(它看起来像一个空格)。去图 - 我想这是一个错误。

这在 IE8 中不会发生。(将 textContent 更改为innerText)那里有一个不同的错误。当您按 Enter 键时,它会将节点拆分为 2 个节点,就像在 Firefox 中一样,但是这些节点中每个节点的“数据”属性随后变为“未定义”。

我敢肯定,这里发生的事情远比我们看到的要多得多,因此对此事的任何意见都将具有启发性。

<!DOCTYPE html>
<html>
<HEAD>
<SCRIPT type="text/javascript">
    function htmlToText(elem) {
        var outText="";
        for(var x=0; x<elem.childNodes.length; x++){
            if(elem.childNodes[x].data){
                outText+=elem.childNodes[x].data;
            }else{
                outText+="\n";
            }
        }
        alert(elem.childNodes.length + " Nodes: \r\n\r\n" + outText);
        return(outText);
    }
</SCRIPT>
</HEAD>
<body>

<div style="white-space:pre;" contenteditable=true id=test>Text in a pre element
is displayed in a fixed-width
font, and it preserves
both      spaces and
line breaks
</DIV>
<INPUT type=button value="submit" onclick="document.getElementById('test2').textContent=htmlToText(document.getElementById('test'))">
<PRE id=test2>
</PRE>
</body>
</html>
对我来说效果很好(在 FF 和 Chrome 中)。还没有针对其他$.browser选项对其进行计算评估,但鉴于 Jquery 不再提供该插件,这更容易插入。我改天会担心性能:)
2021-04-28 16:04:54

这是一个解决方案(使用下划线和 jquery),它似乎适用于 OS X 中的 iOS Safari(iOS 7 和 8)、Safari 8、Chrome 43 和 Firefox 36,以及 Windows 上的 IE6-11:

_.reduce($editable.contents(), function(text, node) {
    return text + (node.nodeValue || '\n' +
        (_.isString(node.textContent) ? node.textContent : node.innerHTML));
}, '')

在此处查看测试页面:http : //brokendisk.com/code/contenteditable.html

虽然我认为真正的答案是,如果您对浏览器提供的标记不感兴趣,则不应使用该contenteditable属性 - textarea 将是适合该工作的工具。

我使用 contenteditable div 来获得在其中呈现 HTML 的好处,例如突出显示 twitter 等多余字符的文本。不过,我对将该格式保存到我的数据库不感兴趣。
2021-04-28 16:04:54
@Amicable 你试过这个功能了吗?让我知道它是否适合您。还要注意,当您复制/粘贴 HTML 时,通常带有 contenteditable 元素,格式会被保留 - 在这种情况下,您可能希望像 Twitter 那样做并过滤掉标记。
2021-04-29 16:04:54
不错的干净解决方案,但是,它不适用于浏览器与图层不一致的情况。即,chrome 在键入时不包含 div 作为第一个元素,但在您按下 Enter 键后立即包含。我发现这个解决方案并不能很好地处理这种情况。
2021-05-13 16:04:54