Usando .text () para recuperar solo texto no anidado en etiquetas secundarias

386

Si tengo html como este:

<li id="listItem">
    This is some text
    <span id="firstSpan">First span text</span>
    <span id="secondSpan">Second span text</span>
</li>

Estoy tratando de usar .text()para recuperar solo la cadena "Esto es algo de texto", pero si tuviera que decir $('#list-item').text(), obtengo "Esto es algo de textoPrimero texto de extensiónSegundo texto de extensión".

¿Hay alguna manera de obtener (y posiblemente eliminar, a través de algo como .text("")) solo el texto libre dentro de una etiqueta, y no el texto dentro de sus etiquetas secundarias?

El HTML no fue escrito por mí, así que esto es con lo que tengo que trabajar. Sé que sería simple simplemente envolver el texto en etiquetas al escribir el html, pero nuevamente, el html está preescrito.

MegaMatt
fuente
Debido a que aún no tengo suficiente reputación para comentar y no deseo que se pierda el conocimiento (espero que ayude a alguien más), una combinación de respuesta de macio.Jun ' , un RegExp e iStranger' para reemplazar un textNode con HTML en Javascript? me permitió buscar nodos de solo texto para una cadena y reemplazar todas las apariciones con enlaces.
JDQ

Respuestas:

509

Me gustó esta implementación reutilizable basada en el clone()método que se encuentra aquí para obtener solo el texto dentro del elemento padre.

Código provisto para una fácil referencia:

$("#foo")
    .clone()    //clone the element
    .children() //select all the children
    .remove()   //remove all the children
    .end()  //again go back to selected element
    .text();
DotNetWala
fuente
55
Con esta solución solo obtienes el texto sin el hijo, pero no puedes reemplazar solo el texto.
BenRoe
1
No obtengo 1 cosa: si .end () vuelve al elemento seleccionado, entonces text () debería copiar el texto original con elementos secundarios. Pero en la práctica veo que el texto de nuestro clon manipulado se está copiando. Entonces end () vuelve a clonar ()?
68
Esta es una forma realmente ineficiente de hacer esto
Billyonecan
55
@billyonecan, ¿puede sugerir un método más eficiente? Esto es atractivo porque es "limpio" y "corto". ¿Que sugieres?
derekmx271
1
@ derekmx271 eche un vistazo a la respuesta de Stuart
billyonecan
364

Respuesta simple:

$("#listItem").contents().filter(function(){ 
  return this.nodeType == 3; 
})[0].nodeValue = "The text you want to replace with" 
macio.Jun
fuente
38
No entiendo por qué las respuestas eficientes (que no generan estructuras de datos extrañas) no se votan tanto como las respuestas que parecen menos aterradoras. +5 si pudiera.
Steven Lu
16
la respuesta simple y eficiente
Paul Carroll
99
¡Esto no solo es más eficiente sino también correcto! Esta solución atiende situaciones en las que el texto está disperso entre elementos secundarios. +5
Kyryll Tenin Baum
15
Para ser aún más claro, si usa IE8 +, puede usar en this.nodeType == Node.TEXT_NODElugar de this.nodeType == 3. Más fácil de leer y entender IMO.
NorTicUs
8
Esto se romperá si lo usa en algo sin texto. Si está utilizando esto como una función y tiene un escenario en el que puede o no tener texto, simplemente capture la .contents().filter(...)llamada en una variable local y verifique su longitud, por ejemplo, var text = $(this).contents().filter(...); if (text.length) { return text[0].nodeValue; } return "";
Carl Bussema
158

Esto parece un caso de uso excesivo de jquery para mí. Lo siguiente tomará el texto ignorando los otros nodos:

document.getElementById("listItem").childNodes[0];

Tendrá que recortar eso, pero obtendrá lo que desea en una sola línea fácil.

EDITAR

Lo anterior obtendrá el nodo de texto . Para obtener el texto real, use esto:

document.getElementById("listItem").childNodes[0].nodeValue;
rg88
fuente
31
La mejor respuesta es que no se necesita un complemento para esto o una cadena de 10 llamadas jQuery. $('.foo')[0].childNodes[0].nodeValue.trim()
Raine
55
¿Qué pasa si el contenido de texto se divide en varios nodos (como una secuencia de crlf, text, crlf)? ¿hay alguna garantía (rael-life) de que el dom construido por la ua utilizará la estructura más simple?
collapsar el
55
Totalmente la mejor respuesta ... ¿por qué otras personas a veces usan jQuery?
ncubica 19/11
11
Esto solo funciona en el caso de <div id = "listItem"> texto que desea <span> otro </span> </div>. No funcionará para <div id = "listItem"> <span> otro </span> texto que desee </div>
Spencer el
1
A veces no tienes document. Vine aquí usando cheerio.
Flash
67

Más fácil y rápido:

$("#listItem").contents().get(0).nodeValue
WakeupMañana
fuente
¿Es compatible este navegador cruzado?
Rajat Gupta
Por supuesto, recupera uno de los elementos que coincide con el objeto jQuery dado por el índice: Jquery Docs .get () .
WakeupMorning
1
@Nate En caso de que necesite usarlo en una etiqueta <br/>, puede usar la respuesta de macio.Jun .
WakeupMorning
Esta debería ser la respuesta aceptada.
Danny
2
¿Por qué en get(0)lugar de solo [0]?
Clonkex
28

Similar a la respuesta aceptada, pero sin clonación:

$("#foo").contents().not($("#foo").children()).text();

Y aquí hay un complemento jQuery para este propósito:

$.fn.immediateText = function() {
    return this.contents().not(this.children()).text();
};

Aquí se explica cómo usar este complemento:

$("#foo").immediateText(); // get the text without children
DUzun
fuente
¿Qué es t en t.children ()?
FrEaKmAn
Esta es una solución duplicada de la que escribió pbjk en enero del 15 ... no obstante, se ve bien.
Oskar Holmkratz
1
En realidad no, @Oskar. ¡La .contents()parte es crítica aquí!
DUzun
Mala solución si sus nodos no usan identificadores.
AndroidDev
3
@AndroidDev Siempre puedes reemplazar el selector con lo que sea que funcione para ti. ¡Esto es solo para ilustrar la técnica! También agregué una versión de complemento para mostrar que funciona incluso sin ID
DUzun
8

no es el código:

var text  =  $('#listItem').clone().children().remove().end().text();

solo convirtiéndome en jQuery por el bien de jQuery? Cuando las operaciones simples implican tantos comandos encadenados y tanto procesamiento (innecesario), tal vez sea hora de escribir una extensión jQuery:

(function ($) {
    function elementText(el, separator) {
        var textContents = [];
        for(var chld = el.firstChild; chld; chld = chld.nextSibling) {
            if (chld.nodeType == 3) { 
                textContents.push(chld.nodeValue);
            }
        }
        return textContents.join(separator);
    }
    $.fn.textNotChild = function(elementSeparator, nodeSeparator) {
    if (arguments.length<2){nodeSeparator="";}
    if (arguments.length<1){elementSeparator="";}
        return $.map(this, function(el){
            return elementText(el,nodeSeparator);
        }).join(elementSeparator);
    }
} (jQuery));

llamar:

var text = $('#listItem').textNotChild();

los argumentos son en caso de que se encuentre un escenario diferente, como

<li>some text<a>more text</a>again more</li>
<li>second text<a>more text</a>again more</li>

var text = $("li").textNotChild(".....","<break>");

el texto tendrá valor:

some text<break>again more.....second text<break>again more
Brent
fuente
1
Agradable. ¿Qué tal hacer de esto una solicitud de extracción para la próxima versión de jQuery?
Jared Tomaszewski el
8

Prueba esto:

$('#listItem').not($('#listItem').children()).text()
pbjk
fuente
6

Tendrá que ser algo adaptado a las necesidades, que dependen de la estructura que se le presente. Para el ejemplo que ha proporcionado, esto funciona:

$(document).ready(function(){
     var $tmp = $('#listItem').children().remove();
     $('#listItem').text('').append($tmp);
});

Demostración: http://jquery.nodnod.net/cases/2385/run

Pero depende bastante de que el marcado sea similar a lo que publicaste.


fuente
2
Cuidado con el futuro lector: el código en esta respuesta mata a los niños en el elemento real. Uno debería usar el clonemétodo aquí si ese no es el efecto deseado.
Mahn
La respuesta de @ DotNetWala, a continuación, y debe usarse en lugar de esta. O al menos, use el .detach()método en lugar de .remove().
Don McCurdy
4
$($('#listItem').contents()[0]).text()

Variante corta de la respuesta de Stuart.

o con get()

$($('#listItem').contents().get(0)).text()
galeksandrp
fuente
4
jQuery.fn.ownText = function () {
    return $(this).contents().filter(function () {
        return this.nodeType === Node.TEXT_NODE;
    }).text();
};
Valiente delfín
fuente
1
Gracias por este fragmento de código, que puede proporcionar ayuda inmediata. Una explicación adecuada mejoraría enormemente su valor educativo al mostrar por qué esta es una buena solución al problema, y ​​la haría más útil para futuros lectores con preguntas similares, pero no idénticas. Por favor, editar su respuesta para agregar explicación y dar una indicación de lo que se aplican limitaciones y supuestos.
Toby Speight
3

Esta es una vieja pregunta, pero la respuesta principal es muy ineficiente. Aquí hay una mejor solución:

$.fn.myText = function() {
    var str = '';

    this.contents().each(function() {
        if (this.nodeType == 3) {
            str += this.textContent || this.innerText || '';
        }
    });

    return str;
};

Y solo haz esto:

$("#foo").myText();
rotaercz
fuente
3

Supongo que esta sería una buena solución también, si desea obtener el contenido de todos los nodos de texto que son hijos directos del elemento seleccionado.

$(selector).contents().filter(function(){ return this.nodeType == 3; }).text();

Nota: la documentación de jQuery utiliza un código similar para explicar la función de contenido: https://api.jquery.com/contents/

PD: También hay una forma un poco más fea de hacerlo, pero esto muestra más en profundidad cómo funcionan las cosas y permite un separador personalizado entre los nodos de texto (tal vez desee un salto de línea allí)

$(selector).contents().filter(function(){ return this.nodeType == 3; }).map(function() { return this.nodeValue; }).toArray().join("");
mvmn
fuente
1

Propongo usar createTreeWalker para encontrar todos los elementos de texto que no están adjuntos a elementos html (esta función se puede usar para extender jQuery):

function textNodesOnlyUnder(el) {
  var resultSet = [];
  var n = null;
  var treeWalker  = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, function (node) {
    if (node.parentNode.id == el.id && node.textContent.trim().length != 0) {
      return NodeFilter.FILTER_ACCEPT;
    }
    return NodeFilter.FILTER_SKIP;
  }, false);
  while (n = treeWalker.nextNode()) {
    resultSet.push(n);
  }
  return resultSet;
}



window.onload = function() {
  var ele = document.getElementById('listItem');
  var textNodesOnly = textNodesOnlyUnder(ele);
  var resultingText = textNodesOnly.map(function(val, index, arr) {
    return 'Text element N. ' + index + ' --> ' + val.textContent.trim();
  }).join('\n');
  document.getElementById('txtArea').value = resultingText;
}
<li id="listItem">
    This is some text
    <span id="firstSpan">First span text</span>
    <span id="secondSpan">Second span text</span>
</li>
<textarea id="txtArea" style="width: 400px;height: 200px;"></textarea>

gaetanoM
fuente
1

Si la posición indexdel nodo de texto está fija entre sus hermanos, puede usar

$('parentselector').contents().eq(index).text()
inarilo
fuente
1

No estoy seguro de cuán flexible o cuántos casos necesita para cubrir, pero para su ejemplo, si el texto siempre viene antes de las primeras etiquetas HTML, ¿por qué no simplemente dividir el html interno en la primera etiqueta y tomar la primera:

$('#listItem').html().split('<span')[0]; 

y si lo necesitas más ancho tal vez solo

$('#listItem').html().split('<')[0]; 

y si necesita el texto entre dos marcadores, como después de una cosa pero antes de otra, puede hacer algo como (no probado) y usar declaraciones if para que sea lo suficientemente flexible como para tener un marcador de inicio o fin o ambos, evitando al mismo tiempo los errores de referencia nula :

var startMarker = '';// put any starting marker here
var endMarker = '<';// put the end marker here
var myText = String( $('#listItem').html() );
// if the start marker is found, take the string after it
myText = myText.split(startMarker)[1];        
// if the end marker is found, take the string before it
myText = myText.split(endMarker)[0];
console.log(myText); // output text between the first occurrence of the markers, assuming both markers exist.  If they don't this will throw an error, so some if statements to check params is probably in order...

Generalmente hago funciones de utilidad para cosas útiles como esta, las hago libres de errores y luego confío en ellas con frecuencia una vez que son sólidas, en lugar de reescribir siempre este tipo de manipulación de cadenas y arriesgar referencias nulas, etc. De esa manera, puede reutilizar la función en muchos proyectos y nunca más tendrá que perder el tiempo depurando por qué una referencia de cadena tiene un error de referencia indefinido. Puede que no sea el código de 1 línea más corto de la historia, pero después de tener la función de utilidad, es una línea a partir de ese momento. Tenga en cuenta que la mayor parte del código solo maneja los parámetros que existen o no para evitar errores :)

Por ejemplo:

/**
* Get the text between two string markers.
**/
function textBetween(__string,__startMark,__endMark){
    var hasText = typeof __string !== 'undefined' && __string.length > 0;
    if(!hasText) return __string;
    var myText = String( __string );
    var hasStartMarker = typeof __startMark !== 'undefined' && __startMark.length > 0 && __string.indexOf(__startMark)>=0;
    var hasEndMarker =  typeof __endMark !== 'undefined' && __endMark.length > 0 && __string.indexOf(__endMark) > 0;
    if( hasStartMarker )  myText = myText.split(__startMark)[1];
    if( hasEndMarker )    myText = myText.split(__endMark)[0];
    return myText;
}

// now with 1 line from now on, and no jquery needed really, but to use your example:
var textWithNoHTML = textBetween( $('#listItem').html(), '', '<'); // should return text before first child HTML tag if the text is on page (use document ready etc)
OG Sean
fuente
si necesita reemplazar texto, simplemente use $('#listItem').html( newHTML ); donde newHTML es una variable que ya tiene el texto despojado.
OG Sean
0

Esta es una buena manera para mi

   var text  =  $('#listItem').clone().children().remove().end().text();
Mif.ComicVN
fuente
1
Esto es exactamente lo mismo que la respuesta de DotNetWala .
Todos los trabajadores son esenciales
0

Se me ocurrió una solución específica que debería ser mucho más eficiente que la clonación y modificación del clon. Esta solución solo funciona con las siguientes dos reservas, pero debería ser más eficiente que la solución actualmente aceptada:

  1. Solo recibes el texto
  2. El texto que desea extraer está antes de los elementos secundarios.

Dicho esto, aquí está el código:

// 'element' is a jQuery element
function getText(element) {
  var text = element.text();
  var childLength = element.children().text().length;
  return text.slice(0, text.length - childLength);
}
Yu Jiang Tham
fuente
0

Al igual que la pregunta, yo estaba tratando de extraer texto con el fin de hacer alguna sustitución de expresiones regulares del texto, pero estaba recibiendo problemas donde mis elementos internos (es decir: <i>, <div>,<span> , etc.) también se iban a quitar.

El siguiente código parece funcionar bien y resolvió todos mis problemas.

Utiliza algunas de las respuestas proporcionadas aquí, pero en particular, solo sustituirá el texto cuando el elemento sea de nodeType === 3.

$(el).contents().each(function() { 
  console.log(" > Content: %s [%s]", this, (this.nodeType === 3));

  if (this.nodeType === 3) {
    var text = this.textContent;
    console.log(" > Old   : '%s'", text);

    regex = new RegExp("\\[\\[" + rule + "\\.val\\]\\]", "g");
    text = text.replace(regex, value);

    regex = new RegExp("\\[\\[" + rule + "\\.act\\]\\]", "g");
    text = text.replace(regex, actual);

    console.log(" > New   : '%s'", text);
    this.textContent = text;
  }
});

Lo que hace lo anterior es recorrer todos los elementos de lo dado el(que simplemente se obtuvo con $("div.my-class[name='some-name']");. Para cada elemento interno, básicamente los ignora. Para cada porción de texto (según lo determinado porif (this.nodeType === 3) ) aplicará la sustitución de expresiones regulares solo a esos elementos .

La this.textContent = textporción simplemente reemplaza la indicación sustitutiva, que en mi caso, yo estaba buscando para los tokens como [[min.val]], [[max.val]], etc.

Este breve extracto de código ayudará a cualquiera que intente hacer lo que la pregunta estaba haciendo ... y un poco más.

Jeach
fuente
-1

solo ponlo en un <p>o<font> y agarrar ese texto $ ( '# listitem fuente'). ()

Lo primero que me vino a la mente

<li id="listItem">
    <font>This is some text</font>
    <span id="firstSpan">First span text</span>
    <span id="secondSpan">Second span text</span>
</li>
Dorjan
fuente
66
No tengo control sobre poner el texto libre en las etiquetas, porque el código con el que estoy trabajando no fue creado por mí. Si pudiera captar solo ese texto, podría eliminarlo y reemplazarlo con etiquetas a su alrededor, o hacer lo que quiera. Pero de nuevo, el html ya está preescrito.
MegaMatt
ah ok Entonces creo que tendrá que filtrar los resultados: lo siento.
Dorjan
-1

Puedes probar esto

alert(document.getElementById('listItem').firstChild.data)
achakravarty
fuente
-2

Use una condición adicional para verificar si innerHTML e innerText son iguales. Solo en esos casos, reemplace el texto.

$(function() {
$('body *').each(function () {
    console.log($(this).html());
    console.log($(this).text());
    if($(this).text() === "Search" && $(this).html()===$(this).text())  {
        $(this).html("Find");
    }
})
})

http://jsfiddle.net/7RSGh/

Paul Verschoor
fuente
-2

Para poder recortar el resultado, use DotNetWala así:

$("#foo")
    .clone()    //clone the element
    .children() //select all the children
    .remove()   //remove all the children
    .end()  //again go back to selected element
    .text()
    .trim();

Descubrí que usar la versión más corta como document.getElementById("listItem").childNodes[0]no funcionará con trim () de jQuery.

Marion Go
fuente
3
Eso es porque document.getElementById("listItem").childNodes[0]es javascript simple, tendrías que envolverlo en la función jQuery$(document.getElementById("listItem").childNodes[0]).trim()
Red Taz
Vale, eso tiene sentido. Jaja. ¡Gracias!
Marion Go
1
Esto es casi idéntico a la respuesta de DotNetWala . Todo lo que hiciste fue agregado .trim()al final. ¿Es necesaria esta respuesta?
Todos los trabajadores son esenciales
-3

No soy un experto en jquery, pero ¿qué tal,

$('#listItem').children().first().text()
Sudheera
fuente
1
Si nota que es un experto en jquery, ¿por qué no convertirse en un experto al leer primero las otras respuestas? ... Una de ellas resultó ser prácticamente la misma que escribió, con los comentarios a continuación que explican por qué no es así. una buena idea.
Oskar Holmkratz
-4

Esto no ha sido probado, pero creo que puede intentar algo como esto:

 $('#listItem').not('span').text();

http://api.jquery.com/not/

El guapo
fuente
3
Porque es lo mismo que $('#listItem').text(). #listItemno es una <span>suma, así not('span')que no hace nada.
Thomas Higginbotham