Извлечение текста из contentEditable div

у меня div установлен в contentEditable и в стиле с "white-space:pre" таким образом, он сохраняет такие вещи, как linebreaks. В Safari, FF и IE div в значительной степени выглядит и работает одинаково. Все хорошо. То, что я хочу сделать, это извлечь текст из этот div, но таким образом, чтобы не потерять форматирование, в частности, разрывы строк.

мы используем jQuery, чей text() функция в основном выполняет предварительный заказ DFS и склеивает все содержимое в этой ветви DOM в один кусок. Это теряет форматирование.

я посмотрел на html() функция, но кажется, что все три браузера делают разные вещи с фактическим HTML, который генерируется за кулисами в my contentEditable div. Предполагая, что я набираю это в свой 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 таким образом, чтобы сохранить переносы? я бы предпочел не изобретать разборное колесо, если мне не нужно.

обновление: я cribbed getText функция из jQuery 1.4.2 и изменила ее, чтобы извлечь ее с пробелами, в основном неповрежденными (я только chnaged одну строку, где я добавляю новую строку);

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

я вызываю эту функцию и использую ее вывод, чтобы назначить ее узлу XML с jQuery, что-то вроде:

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

полученный XML в конечном итоге отправляется на сервер через AJAX вызов.

это хорошо работает в Safari и Firefox.

на IE, только первый 'n', кажется, каким-то образом сохраняется. Глядя на него больше, похоже, что jQuery устанавливает текст так (строка 4004 jQuery-1.4.2.в JS):

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 пропуска быть обращанным правильно.

выше #edit идентификатор contentEditable компонент, поэтому просто измените это или сделайте это функцией, например:

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(), вы можете проверить, что версия здесь.


Я забыл об этом вопросе до сих пор, когда Нико шлепнул по нему награду.

Я решил проблему, написав функцию, в которой я нуждался, взяв функцию из существующей кодовой базы 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;
}

посмотреть этот скрипка

или этот пост

как анализировать редактируемый текст 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.

есть проблема, tho. Когда вы нажмете enter в конце любой строки исходного текста, вместо вставляя LF, он вставляет "Â". Вы можете нажать enter еще раз, и он помещает туда LF, но не в первый раз. И вы должны удалить " Â " (это выглядит как пробел). Иди разберись - я думаю, это жучок.

это не происходит в IE8. (измените textContent на innerText) там есть другая ошибка, tho. Когда вы нажимаете 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>

вот решение (с использованием подчеркивания и jquery), которое, похоже, работает в iOS Safari (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 будет правильным инструментом для работа.


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