Извлечение текста из div contentEditable - PullRequest
45 голосов
/ 11 августа 2010

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

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

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

Обновление: я извлек функцию getText из jQuery 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;
}

Я вызываю эту функцию и использую ее вывод для присвоения ее узлу 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 ]

36 голосов
/ 12 ноября 2010

К сожалению, вам все равно придется обрабатывать это для случая 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(), , вы можете проверить эту версию здесь .

4 голосов
/ 10 ноября 2010

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

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

Я обнаружил это сегодня в 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.

Есть проблема, хотя.Когда вы нажимаете ввод в конце любой строки исходного текста, вместо ввода LF, он вводит «В». Вы можете нажать ввод еще раз, и это вводит LF, но не в первый раз.И вы должны удалить «В» (это выглядит как пробел).Пойди разберись - думаю, это ошибка.

Этого не происходит в IE8.(измените textContent на innerText) Там есть другая ошибка, хотя.Когда вы нажимаете Enter, он разделяет узел на 2 узла, как это происходит в Firefox, но свойство «data» каждого из этих узлов становится «неопределенным».

Я уверен, что есть гораздо большездесь происходит, чем кажется на первый взгляд, поэтому любой вклад в этот вопрос будет поучительным.

<!DOCTYPE html>



    function htmlToText(elem) {
        var outText="";
        for(var x=0; x



Text in a pre element
is displayed in a fixed-width
font, and it preserves
both      spaces and
line breaks



1 голос
/ 11 октября 2012
0 голосов
/ 14 июня 2017
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;
}
0 голосов
/ 25 февраля 2015

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

...