从contentEditable div中提取文本

时间:2010-08-11 06:48:23

标签: javascript jquery html css contenteditable

我将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>

Firefox 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;
}

我调用此函数并使用其输出将其分配给带有jQuery的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 个答案:

答案 0 :(得分:36)

不幸的是,你仍然必须为每个浏览器单独处理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();

You can test it out here。特别是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();
}

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来调用它。

答案 1 :(得分:4)

到目前为止,我已经忘记了这个问题,当时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;
}

答案 2 :(得分:1)

请参阅此fiddle

或者这篇文章

How to parse editable DIV's text with browser compatibility

经过多次努力后创建............

答案 3 :(得分:1)

我今天在Firefox中发现了这个:

我传递了一个满足的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.

有一个问题,那么。当你在原始文本的任何一行的末尾点击输入时,不是输入一个LF,而是输入一个“”。你可以再次点击输入并在那里输入一个LF,但不是第一次。你必须删除“”(它看起来像一个空格)。去图 - 我猜这是一个错误。

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

我确信这里有更多的事情要比满足眼睛,所以对此事的任何意见都会有所启发。

<!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>

答案 4 :(得分:0)

这是一个解决方案(使用下划线和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将是适合这项工作的工具。

答案 5 :(得分:-1)

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;
}