2010-08-11 92 views
44

我将div设置为contentEditable,并使用“white-space:pre”进行样式设置,因此它保留了换行符之类的内容。在Safari,FF和IE中,div看起来很像,工作原理也一样。一切都很好。我想要做的是从这个div中提取文本,但以这种方式不会丢失格式 - 特别是换行符。从contentEditable 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; 
} 

我调用该函数并使用其输出将其分配到使用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的实现可以混搭空白。这是真的还是我做错了什么?

+2

有意思的是,IE浏览器的行为最为理智:contentEditable最初是IE专有的;它从5.5开始就已经在IE了,所以我想他们已经有了最多的时间让它运行良好。 – Yahel 2010-11-12 03:30:57

回答

3

到现在为止,我忘了这个问题,当时妮可给了它一个奖金。

我通过编写自己需要的函数解决了这个问题,从现有的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; 
} 
+0

这也在Chrome中破解 - 1)在分隔线上输入1,2,3,4 2)回到第1行3)键入几个单词4)到第二行开始,按退格键,按回车键,按退格键5)查看结果,第2行将有一个额外的换行符 – 2013-10-16 19:44:51

35

不幸的是你还是要处理这个对每个单独的浏览器的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调用它。

+0

Ick。正如你所看到的,浏览器检测是不好的。幸运的是,这是可以避免的:查看我的答案。 – 2010-11-14 15:43:41

+0

@Tim - 我无法让你的方法在IE或Opera中工作:http://www.jsfiddle.net/UjZEN/3/ – 2010-11-14 15:48:34

+0

任何更新?你是否能够在所有浏览器上完全解决它? – gsagrawal 2011-11-30 08:25:09

1

我今天这个发现在Firefox:

我通过一个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. 

有一个问题,寿。当你在原文的任何一行的末尾敲入回车符,而不是放入一个LF时,它会放入一个“”。你可以再次敲入回车,并在那里放入一个LF,但不是第一次。你必须删除“”(它看起来像一个空格)。去图 - 我想这是一个错误。

这不会发生在IE8中。 (将textContent更改为innerText)这里有一个不同的bug,tho。当你敲回车时,它将节点拆分成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> 
+0

适用于我(FF和Chrome)。没有对其他'$ .browser'选项进行计算评估,但鉴于jquery不再提供该插件,这更容易下降。我会担心另一天的表现:) – Oli 2014-11-06 22:49:58

0

这里(用下划线和jQuery)的解决方案,这似乎在Safari浏览器的iOS工作(iOS的7,8),Safari浏览器8时,Chrome 43和Firefox 36在OS X和IE6-11在Windows上:

_.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将是这项工作的适当工具。

+1

我使用contenteditable div为了在其中呈现HTML的好处,例如突出显示像twitter这样的多余字符的文字 我不想将格式保存到我的数据库。 – Amicable 2015-07-10 10:36:54

+0

@Amicable你尝试过这个功能吗?让我知道它是否适合你。另外请注意,当你复制/粘贴HTML格式时,通常会带有一个contenteditable元素 - 你可能想要像Twitter那样做,并在这种情况下过滤出标记。 – 2015-08-18 15:41:53

+0

不错,干净的解决方案,但是,它不适用于浏览器与图层不一致的情况。也就是说,chrome在输入时不包含div作为第一个元素,但只要您按Enter键即可。我发现这个解决方案没有完全处理这种情况。 – Lukus 2016-03-22 20:49:36

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

嗨!感谢您的回答,欢迎来到Stackoverflow。请查看[如何回答](https://stackoverflow.com/help/how-to-answer)并尝试改进您的答案。添加一个关于OP如何出错或者你的代码做得更好的解释有助于提高答案的质量。 – Ortund 2017-06-14 14:51:02