2010-08-11 6 views
44

У меня есть набор div для contentEditable и стилизованный под «white-space:pre», поэтому он хранит такие вещи, как linebreaks. В Safari, FF и IE, div в значительной степени выглядит и работает одинаково. Все хорошо. То, что я хочу сделать, - извлечь текст из этого div, но таким образом, чтобы не потерять форматирование - в частности, разрыв строки.Извлечение текста из contentEditable 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> 

Ugh. Здесь нет ничего очень последовательного. Удивительно, что MSIE выглядит наиболее разумно! (Заглавная тег P и все)

У дивана будет динамически установлен стиль (шрифт, цвет, размер и выравнивание), который выполняется с использованием CSS, поэтому я не уверен, могу ли я использовать тег pre (который был на некоторых страницах, которые я нашел с помощью Google).

Кто-нибудь знает какой-либо код JavaScript и/или плагин jQuery или что-то, что будет извлекать текст из contentEditable div таким образом, чтобы сохранить разрывы строк? Я бы предпочел не изобретать синтаксический руль, если мне это не нужно.

Обновление: я скрепил функцию 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 может смять пробелы , Это правда или я делаю что-то неправильно?

+2

Интересно, что неудивительно, что IE действует наиболее разумно: contentEditable изначально принадлежал IE; он был в IE с 5,5, так что, я думаю, у них было больше времени, чтобы заставить его работать хорошо. – Yahel

ответ

3

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

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

Я протестировал эту функцию с помощью Safari (WebKit), IE, Firefox и Opera. Я не стал проверять какие-либо другие браузеры, так как вся информация для контента нестандартная. Также возможно, что обновление для любого браузера может нарушить эту функцию, если они изменят способ реализации 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 будет иметь дополнительный разрыв строки после нее. –

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 прохода.

В приведенном выше #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(); 
} 

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. Как вы заметили, обнаружение браузера плохое. К счастью, этого можно избежать здесь: см. Мой ответ. –

+0

@Tim - я не мог получить ваш подход к работе в IE или Opera, хотя: http://www.jsfiddle.net/UjZEN/3/ –

+0

любое обновление по этому вопросу? могли ли вы разрешить его полностью во всех браузерах ??? – gsagrawal

1

Я обнаружил это сегодня в Firefox:

Пропускаю контент-контент, для которого белый пробел установлен на «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 туда, но не в первый раз. И вам нужно удалить «Â» (это выглядит как пробел). Go figure - Я думаю, это ошибка.

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

Хорошо работает для меня (в FF и Chrome). Не вычисляли его вычислительно против других опций '$ .browser', но, учитывая, что JQuery больше не отправляет этот плагин, это было легче зайти. Я буду беспокоиться о производительности в другой день :) – Oli

0

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

+1

Я использую контент-доступный div для преимуществ визуализации HTML внутри него, например текст выделяет лишние символы, такие как твиттер. Я не заинтересован в сохранении этого форматирования в моей базе данных. – Amicable

+0

@Amicable Вы попробовали функцию? Дайте мне знать, если это сработает для вас. Также имейте в виду, что, как правило, без элемента contenteditable при копировании/вставке HTML форматирование сохраняется - вы, вероятно, захотите сделать так, как это делает Twitter, и отфильтровать разметку в этой ситуации. –

+0

Хорошее чистое решение, однако, оно не работает для случаев, когда браузер несовместим со слоями. I.e., chrome не включает div как первый элемент при вводе, но делает, как только вы нажимаете enter. Я нашел, что это решение не совсем справилось с этим делом. – Lukus

-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

Смежные вопросы