2010-08-11 24 views
44

Tengo un div establecido en contentEditable y con el estilo "white-space:pre", por lo que guarda cosas como saltos de línea. En Safari, FF e IE, el div se ve bastante y funciona de la misma manera. Todo está bien. Lo que quiero hacer es extraer el texto de este div, pero de tal manera que no pierda el formato, específicamente, la línea se rompe.Extrayendo texto de un contentEditable div

Estamos utilizando jQuery, cuya función text() básicamente hace un pre-orden de DFS y pega todo el contenido en esa rama del DOM en un solo trozo. Esto pierde el formateo.

He echado un vistazo a la función html(), pero parece que los tres navegadores hacen cosas diferentes con el HTML real que se genera detrás de las escenas en mi div contentEditable. Suponiendo que escribo esto en mi div:

1 
2 
3 

Estos son los resultados:

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. Nada muy consistente aquí. ¡Lo sorprendente es que MSIE se ve más sensata! (Etiqueta P en mayúscula y todo)

El div tendrá un diseño dinámico de estilo (cara de fuente, color, tamaño y alineación) que se realiza mediante CSS, así que no estoy seguro si puedo usar una etiqueta pre (que era aludido en algunas páginas que encontré usando Google).

¿Alguien sabe de algún código JavaScript y/o jQuery plugin o algo que extraiga texto de un contentEditable div de tal manera que se preserve linebreaks? Preferiría no reinventar una rueda de análisis si no fuera necesario.

Actualización: Comprimí la función getText de jQuery 1.4.2 y la modifiqué para extraerla con espacios en blanco casi intactos (solo conecté una línea donde agregué una nueva línea);

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

me llaman a esta función y el uso de su salida para asignarlo a un nodo XML con jQuery, algo así como:

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

El XML resultante es finalmente enviada a un servidor a través de una llamada AJAX.

Esto funciona bien en Safari y Firefox.

En IE, solo la primera '\ n' parece retenerse de alguna manera. Mirar en él más, parece que jQuery es establecer el texto como tal (línea 4004 de jQuery-1.4.2.js):

return this.empty().append((this[0] && this[0].ownerDocument || document).createTextNode(text)); 

Leyendo sobre createTextNode, parece que la aplicación de IE puede triturar hasta el espacio en blanco . ¿Es esto cierto o estoy haciendo algo mal?

+2

Curiosamente, no es de extrañar que IE esté actuando de la manera más sensata: contentEditable fue originalmente propiedad de IE; ha estado en IE desde 5.5, así que supongo que han tenido más tiempo para hacerlo funcionar bien. – Yahel

Respuesta

3

Me olvidé de esta pregunta hasta ahora, cuando Nico le dio una bofetada.

Resolví el problema escribiendo la función que necesitaba, reduciendo una función de la base de código de jQuery existente y modificándola para que funcionase como lo necesitaba.

He probado esta función con Safari (WebKit), IE, Firefox y Opera. No me molesté en consultar otros navegadores, ya que todo el contenido editable no es estándar. También es posible que una actualización de cualquier navegador pueda romper esta función si cambian la forma en que implementan contentEditable. Entonces, tenga cuidado con el programador.

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

esto también se rompe en Chrome - 1) ingrese 1,2,3,4 en líneas separadas 2) regrese a la línea 1 3) escriba unas pocas palabras 4) vaya al comienzo de la línea dos, presione la tecla de retroceso, presione enter, presione la tecla de retroceso 5) vea los resultados, la línea 2 tendrá un salto de línea adicional después de –

35

Desafortunadamente usted todavía tiene que manejar esto para el caso pre de forma individual por el navegador (No justifico navegador detección en muchos casos, el uso función de detección ... pero en este caso se trata de es necesario), pero por suerte que puede hacerse cargo de todos ellos bastante concisa, así:

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 en particular es una molestia debido a la forma en que se hace &nbsp; y nuevas líneas en la conversión de texto, es por eso que obtiene el tratamiento <br> anterior para que sea coherente, por lo que necesita 2 pases para ser manejado correctamente.

En lo anterior #edit es el ID del componente contentEditable, por lo que acaba de cambiar que fuera, o hacer de esto una función, por ejemplo:

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. O, ya que esta se basa en métodos de jQuery todos modos, lo convierten en un plugin, así:

$.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(); 
}; 

a continuación, puedes llamarlo con $("#edit").getPreText(), you can test that version here.

+0

Ick. Como observa, la detección del navegador es mala. Afortunadamente, es evitable aquí: ver mi respuesta. –

+0

@Tim - No pude conseguir su enfoque para trabajar en IE u Opera: http://www.jsfiddle.net/UjZEN/3/ –

+0

¿Alguna actualización sobre esto? ¿Pudiste resolverlo completamente en todos los navegadores? – gsagrawal

1

descubrí esto hoy en Firefox:

Paso un div satisfactorio, cuyo espacio en blanco está configurado como "pre" para esta función, y funciona de forma abrupta.

Agregué una línea para mostrar cuántos nodos hay, y un botón que coloca la salida en otro PRE, solo para demostrar que los saltos de línea están intactos.

Básicamente dice esto:

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. 

hay un problema, aunque. Cuando pulses enter al final de cualquier línea del texto original, en lugar de poner un LF, ingresa una "Â". Puedes presionar enter nuevamente y pone un LF allí, pero no la primera vez. Y debe eliminar la "Â" (parece un espacio). Ir a la figura - Supongo que es un error.

Esto no ocurre en IE8. (cambiar textContent a innerText) Hay un error diferente allí, aunque. Cuando presionas enter, divide el nodo en 2 nodos, como lo hace en Firefox, pero la propiedad "data" de cada uno de esos nodos se convierte en "indefinido".

Estoy seguro de que aquí está sucediendo mucho más de lo que parece, así que cualquier comentario sobre el tema será esclarecedor.

<!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

Funciona bien para mí (en FF y Chrome). No lo he evaluado computacionalmente en comparación con las otras opciones '$ .browser', pero dado que Jquery ya no envía ese complemento, fue más fácil hacerlo. Me preocuparé por el rendimiento otro día :) – Oli

0

aquí es una solución (mediante subrayado y jQuery) que parece funcionar en IOS Safari (iOS 7 y 8), Safari 8, Chrome 43, 36 y Firefox en OS X, y IE6-11 en Windows:

_.reduce($editable.contents(), function(text, node) { 
    return text + (node.nodeValue || '\n' + 
     (_.isString(node.textContent) ? node.textContent : node.innerHTML)); 
}, '') 

página de prueba se ve aquí: http://brokendisk.com/code/contenteditable.html

aunque creo que la verdadera respuesta es que si usted no está interesado en el margen de beneficio que proporciona el navegador, no debe estar usando el atributo contenteditable - una textarea sería la herramienta adecuada para el trabajo.

+1

Utilizo un div satisfactorio por los beneficios de renderizar HTML dentro de él, por ejemplo texto resaltando caracteres en exceso como Twitter. No estoy interesado en guardar ese formato en mi base de datos. – Amicable

+0

@Amicable ¿Has probado la función? Avíseme si parece funcionar para usted. También tenga en cuenta que, por lo general, con un elemento contento cuando copia/pega HTML, se conserva el formato; es probable que desee hacer lo que Twitter hace y filtrar el marcado en esta situación. –

+0

Buena solución limpia, sin embargo, no funciona en los casos en que el navegador no es coherente con las capas. Es decir, Chrome no incluye un div como el primer elemento al escribir pero lo hace tan pronto como presiona intro. Encontré que esta solución no manejó bien ese caso. – 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

¡Hola! Gracias por su respuesta y bienvenido a Stackoverflow. Eche un vistazo a [cómo responder] (https://stackoverflow.com/help/how-to-answer) e intente mejorar un poco su respuesta. Agregar una explicación sobre cómo funcionaba el PO o cuál es el mejor para tu código ayuda a mejorar la calidad de tu respuesta. – Ortund

Cuestiones relacionadas