46

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

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

我查看了该函数,但似乎所有三个浏览器都使用在我的divhtml()幕后生成的实际 HTML 执行不同的操作。contentEditable假设我在我的 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">

即 8:

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

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

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

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

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

4

6 回答 6

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

你可以在这里测试一下。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()你可以在这里测试那个版本

于 2010-11-12T10:45:59.750 回答
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;
}
于 2010-11-10T00:16:06.070 回答
1

看到这个小提琴

或者这个帖子

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

千辛万苦创造出来的…………

于 2012-10-11T04:58:55.260 回答
1

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

我将一个内容可编辑的 div 传递给此函数,该 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>
于 2013-05-02T23:27:50.090 回答
0

这是一个似乎适用于 iOS Safari(iOS 7 和 8)、OS X 中的 Safari 8、Chrome 43 和 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 将是完成这项工作的合适工具。

于 2015-02-25T01:21:49.513 回答
-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;
}
于 2017-06-14T14:45:40.777 回答