【问题标题】:Extracting text from a contentEditable div从 contentEditable div 中提取文本
【发布时间】:2011-03-28 05:20:56
【问题描述】:

我有一个 div 设置为 contentEditable 并使用“white-space:pre”设置样式,因此它保留了换行符之类的内容。在 Safari、FF 和 IE 中,div 的外观和工作方式几乎相同。一切都很好。我想要做的是从这个 div 中提取文本,但不会丢失格式——特别是换行符。

我们使用的是 jQuery,它的text() 函数基本上是进行预排序 DFS 并将 DOM 分支中的所有内容粘合到一个块中。这会丢失格式。

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

1
2
3

这些是结果:

Safari 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 中提取文本以保留换行符的东西?我不想重新发明解析轮子,如果我不需要的话。

更新:我抄袭了 jQuery 1.4.2 中的 getText 函数,并对其进行了修改以将其提取为几乎完整的空白(我只更改了添加换行符的一行);

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 的实现可能会混搭空白。这是真的还是我做错了什么?

【问题讨论】:

  • 有趣的是,IE 表现得最清醒也就不足为奇了:contentEditable 最初是 IE 专有的;它从 5.5 开始就在 IE 中,所以我猜他们有最多的时间让它运行良好。

标签: javascript jquery html css contenteditable


【解决方案1】:

不幸的是,对于 pre 的情况,您仍然必须针对每个浏览器单独处理此问题(我不容忍 浏览器 在许多情况下检测,请使用 feature 检测...但在这种情况下这是必要的),但幸运的是,您可以非常简洁地处理它们,如下所示:

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();

You can test it out here。 IE 尤其麻烦,因为 &amp;nbsp; 和文本转换中的换行方式是这样的,这就是为什么它得到上面的 &lt;br&gt; 处理以使其保持一致,因此需要 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();
}

You can test that here。或者,由于它是基于 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()you can test that version here来调用它。

【讨论】:

  • 伊克。如您所见,浏览器检测很糟糕。幸运的是,这是可以避免的:请参阅我的答案。
  • @Tim - 我无法让您在 IE 或 Opera 中工作:jsfiddle.net/UjZEN/3
  • 这个(上面的小提琴)在 chrome 中达到平衡... 1)在单独的行上添加 1,2,3,4 2)测试,看起来不错 3)转到第 2 行的开头,按退格 4) 按回车 5) 测试 - 注意第 2、3、4 行现在都在一行
【解决方案2】:

直到现在我才忘记这个问题,当时 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) 到第 2 行的开头,按退格,按回车,按退格 5)查看结果,第2行后面会有一个额外的换行符
【解决方案3】:

看到这个fiddle

或者这篇文章

How to parse editable DIV's text with browser compatibility

历经千辛万苦才创造出来的…………

【讨论】:

    【解决方案4】:

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

    我将一个空白设置为“pre”的 contenteditable div 传递给此函数,并且效果很好。

    我添加了一行来显示有多少个节点,以及一个将输出放入另一个 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 不再提供该插件,这更容易加入。我会担心改天的性能 :)
    【解决方案5】:

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

    _.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。不过,我对将格式保存到我的数据库不感兴趣。
    • @Amicable 你试过这个功能了吗?让我知道它是否对你有用。另请注意,当您复制/粘贴 HTML 时,通常带有 contenteditable 元素,格式会保留 - 您可能希望像 Twitter 那样做,并在这种情况下过滤掉标记。
    • 不错的干净解决方案,但是,它不适用于浏览器与图层不一致的情况。即,chrome 在键入时不包含 div 作为第一个元素,但在您按 Enter 后立即包含。我发现这个解决方案不能很好地处理这种情况。
    【解决方案6】:
    this.editableVal = function(cont, opts) 
    {
      if (!cont) return '';
      var el = cont.firstChild;
      var v = '';
      var contTag = new RegExp('^(DIV|P|LI|OL|TR|TD|BLOCKQUOTE)$');
      while (el) {
        switch (el.nodeType) {
          case 3:
            var str = el.data.replace(/^\n|\n$/g, ' ').replace(/[\n\xa0]/g, ' ').replace(/[ ]+/g, ' ');
            v += str;
            break;
          case 1:
            var str = this.editableVal(el);
            if (el.tagName && el.tagName.match(contTag) && str) {
              if (str.substr(-1) != '\n') {
                str += '\n';
              }
    
              var prev = el.previousSibling;
              while (prev && prev.nodeType == 3 && PHP.trim(prev.nodeValue) == '') {
                prev = prev.previousSibling;
              }
              if (prev && !(prev.tagName && (prev.tagName.match(contTag) || prev.tagName == 'BR'))) {
                str = '\n' + str;
              }
    
            }else if (el.tagName == 'BR') {
              str += '\n';
            }
            v += str;
            break;
        }
        el = el.nextSibling;
      }
      return v;
    }
    

    【讨论】:

    • 嗨!感谢您的回答,欢迎来到 Stackoverflow。请查看how to answer 并尝试改进您的答案。添加关于 OP 如何出错或您的代码做得更好的解释有助于提高答案的质量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-17
    • 1970-01-01
    相关资源
    最近更新 更多