¿Cómo selecciono nodos de texto con jQuery?

388

Me gustaría obtener todos los nodos de texto descendientes de un elemento, como una colección jQuery. ¿Cuál es la mejor manera de hacer eso?

Christian Oudard
fuente

Respuestas:

261

jQuery no tiene una función conveniente para esto. Debe combinar contents(), lo que dará solo nodos secundarios pero incluye nodos de texto, con find(), que da todos los elementos descendientes pero no nodos de texto. Esto es lo que se me ocurrió:

var getTextNodesIn = function(el) {
    return $(el).find(":not(iframe)").addBack().contents().filter(function() {
        return this.nodeType == 3;
    });
};

getTextNodesIn(el);

Nota: Si está utilizando jQuery 1.7 o anterior, el código anterior no funcionará. Para solucionar esto, reemplace addBack()con andSelf(). andSelf()está en desuso a favor de addBack()1.8 en adelante.

Esto es algo ineficiente en comparación con los métodos DOM puros y tiene que incluir una solución fea para la sobrecarga de jQuery de su contents()función (gracias a @rabidsnail en los comentarios por señalar eso), así que aquí hay una solución que no es jQuery que utiliza una función recursiva simple. El includeWhitespaceNodesparámetro controla si los nodos de texto de espacios en blanco se incluyen o no en la salida (en jQuery se filtran automáticamente).

Actualización: error corregido cuando includeWhitespaceNodes es falso.

function getTextNodesIn(node, includeWhitespaceNodes) {
    var textNodes = [], nonWhitespaceMatcher = /\S/;

    function getTextNodes(node) {
        if (node.nodeType == 3) {
            if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)) {
                textNodes.push(node);
            }
        } else {
            for (var i = 0, len = node.childNodes.length; i < len; ++i) {
                getTextNodes(node.childNodes[i]);
            }
        }
    }

    getTextNodes(node);
    return textNodes;
}

getTextNodesIn(el);
Tim Down
fuente
¿Puede pasar el elemento, ser el nombre de un div?
crosenblum
@crosenblum: Puedes llamar document.getElementById()primero, si eso es lo que quieres decir:var div = document.getElementById("foo"); var textNodes = getTextNodesIn(div);
Tim Down
Debido a un error en jQuery, si tiene algún iframes en el, deberá usar .find (': not (iframe)') en lugar de .find ('*').
bobpoekert
@rabidsnail: Creo que el uso de .contents()todos modos implica que también buscará en el iframe. No veo cómo podría ser un error.
Robin Maben
bugs.jquery.com/ticket/11275 Si esto es realmente un error parece ser un tema de debate, pero error o no si llama a find ('*'). contents () en un nodo que contiene un iframe que no agregado a la dom obtendrá una excepción en un punto indefinido.
bobpoekert
209

Jauco publicó una buena solución en un comentario, así que la estoy copiando aquí:

$(elem)
  .contents()
  .filter(function() {
    return this.nodeType === 3; //Node.TEXT_NODE
  });
Christian Oudard
fuente
34
en realidad $ (elem) .contents () .filter (function () {return this.nodeType == Node.TEXT_NODE;}); es suficiente
Jauco
37
IE7 no define el Nodo global, por lo que debe usar this.nodeType == 3, desafortunadamente: stackoverflow.com/questions/1423599/node-textnode-and-ie7
Christian Oudard
17
¿Esto no solo devuelve los nodos de texto que son los hijos directos del elemento en lugar de los descendientes del elemento como lo solicitó el OP?
Tim Down
77
esto no funcionará cuando el nodo de texto esté profundamente anidado dentro de otros elementos, porque el método contenidos () solo devuelve los nodos hijos inmediatos, api.jquery.com/contents
minhajul
1
@Jauco, no, ¡no es suficiente! como .contents () devuelve solo los nodos hijos inmediatos
minhajul
17
$('body').find('*').contents().filter(function () { return this.nodeType === 3; });
El nrik
fuente
6

jQuery.contents()se puede usar con jQuery.filterpara encontrar todos los nodos de texto secundarios. Con un pequeño giro, también puede encontrar nodos de texto de nietos. No se requiere recursividad:

$(function() {
  var $textNodes = $("#test, #test *").contents().filter(function() {
    return this.nodeType === Node.TEXT_NODE;
  });
  /*
   * for testing
   */
  $textNodes.each(function() {
    console.log(this);
  });
});
div { margin-left: 1em; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<div id="test">
  child text 1<br>
  child text 2
  <div>
    grandchild text 1
    <div>grand-grandchild text 1</div>
    grandchild text 2
  </div>
  child text 3<br>
  child text 4
</div>

jsFiddle

Salman A
fuente
4

Estaba obteniendo muchos nodos de texto vacíos con la función de filtro aceptada. Si solo está interesado en seleccionar nodos de texto que contengan espacios en blanco, intente agregar un nodeValuecondicional a su filterfunción, como un simple $.trim(this.nodevalue) !== '':

$('element')
    .contents()
    .filter(function(){
        return this.nodeType === 3 && $.trim(this.nodeValue) !== '';
    });

http://jsfiddle.net/ptp6m97v/

O para evitar situaciones extrañas en las que el contenido se ve como un espacio en blanco, pero no lo es (por ejemplo, el &shy;carácter de guión suave , líneas nuevas \n, pestañas, etc.), puede intentar usar una Expresión regular. Por ejemplo, \Scoincidirá con los caracteres que no sean espacios en blanco:

$('element')
        .contents()
        .filter(function(){
            return this.nodeType === 3 && /\S/.test(this.nodeValue);
        });
Alex W
fuente
3

Si puede suponer que todos los elementos secundarios son Nodos de elemento o Nodos de texto, entonces esta es una solución.

Para obtener todos los nodos de texto secundarios como una colección jquery:

$('selector').clone().children().remove().end().contents();

Para obtener una copia del elemento original con hijos sin texto eliminados:

$('selector').clone().children().remove().end();
colllin
fuente
1
Acabo de notar el comentario de Tim Down sobre otra respuesta. Esta solución solo obtiene los hijos directos, no todos los descendientes.
colllin
2

Por alguna razón contents()no funcionó para mí, así que si no funcionó para usted, aquí hay una solución que hice, creé jQuery.fn.descendantscon la opción de incluir nodos de texto o no

Uso


Obtenga todos los descendientes, incluidos los nodos de texto y los nodos de elementos

jQuery('body').descendants('all');

Obtener todos los descendientes que solo devuelven nodos de texto

jQuery('body').descendants(true);

Obtener todos los descendientes que devuelven solo nodos de elementos

jQuery('body').descendants();

Coffeescript Original :

jQuery.fn.descendants = ( textNodes ) ->

    # if textNodes is 'all' then textNodes and elementNodes are allowed
    # if textNodes if true then only textNodes will be returned
    # if textNodes is not provided as an argument then only element nodes
    # will be returned

    allowedTypes = if textNodes is 'all' then [1,3] else if textNodes then [3] else [1]

    # nodes we find
    nodes = []


    dig = (node) ->

        # loop through children
        for child in node.childNodes

            # push child to collection if has allowed type
            nodes.push(child) if child.nodeType in allowedTypes

            # dig through child if has children
            dig child if child.childNodes.length


    # loop and dig through nodes in the current
    # jQuery object
    dig node for node in this


    # wrap with jQuery
    return jQuery(nodes)

Drop In Versión Javascript

var __indexOf=[].indexOf||function(e){for(var t=0,n=this.length;t<n;t++){if(t in this&&this[t]===e)return t}return-1}; /* indexOf polyfill ends here*/ jQuery.fn.descendants=function(e){var t,n,r,i,s,o;t=e==="all"?[1,3]:e?[3]:[1];i=[];n=function(e){var r,s,o,u,a,f;u=e.childNodes;f=[];for(s=0,o=u.length;s<o;s++){r=u[s];if(a=r.nodeType,__indexOf.call(t,a)>=0){i.push(r)}if(r.childNodes.length){f.push(n(r))}else{f.push(void 0)}}return f};for(s=0,o=this.length;s<o;s++){r=this[s];n(r)}return jQuery(i)}

Versión Javascript no minificada: http://pastebin.com/cX3jMfuD

Este es un navegador cruzado, Array.indexOfse incluye un pequeño polyfill en el código.

iConnor
fuente
1

También se puede hacer así:

var textContents = $(document.getElementById("ElementId").childNodes).filter(function(){
        return this.nodeType == 3;
});

El código anterior filtra los textNodes de los nodos secundarios hijos directos de un elemento dado.

SR verde
fuente
1
... pero no todos los nodos hijos descendientes (por ejemplo, un nodo de texto que es hijo de un elemento que es hijo del elemento original).
Tim Down
0

si desea quitar todas las etiquetas, intente esto

función:

String.prototype.stripTags=function(){
var rtag=/<.*?[^>]>/g;
return this.replace(rtag,'');
}

uso:

var newText=$('selector').html().stripTags();
Rahen Rangan
fuente
0

Para mí, simplemente viejo .contents() parece funcionar para devolver los nodos de texto, solo tiene que tener cuidado con sus selectores para que sepa que serán nodos de texto.

Por ejemplo, esto envolvió todo el contenido de texto de los TD en mi tabla con preetiquetas y no tuvo problemas.

jQuery("#resultTable td").content().wrap("<pre/>")
davenpcj
fuente
0

Tuve el mismo problema y lo resolví con:

Código:

$.fn.nextNode = function(){
  var contents = $(this).parent().contents();
  return contents.get(contents.index(this)+1);
}

Uso:

$('#my_id').nextNode();

Es como next()pero también devuelve los nodos de texto.

Guillermo
fuente
.nextSibling es de la especificación Dom: developer.mozilla.org/en/Document_Object_Model_(DOM)/…
Guillermo