getElementsByTagName () equivalente a textNodes

79

¿Hay alguna forma de obtener la colección de todos textNode objetos dentro de un documento?

getElementsByTagName() funciona muy bien para Elements, pero textNode s no son Elements.

Actualización: me doy cuenta de que esto se puede lograr recorriendo el DOM, como sugieren muchos a continuación. Sé cómo escribir una función DOM-walker que observe cada nodo del documento. Esperaba que hubiera alguna forma nativa del navegador de hacerlo. Después de todo, es un poco extraño que pueda obtener todos los <input>correos electrónicos con una sola llamada integrada, pero no todos textNodelos correos electrónicos.

levik
fuente

Respuestas:

117

Actualización :

He descrito algunas pruebas de rendimiento básicas para cada uno de estos 6 métodos en 1000 ejecuciones. getElementsByTagNamees el más rápido, pero hace un trabajo a medias, ya que no selecciona todos los elementos, sino solo un tipo particular de etiqueta (creo p) y asume ciegamente que su firstChild es un elemento de texto. Puede que tenga algunos defectos, pero está ahí para fines de demostración y para comparar su rendimiento con TreeWalker. Ejecute las pruebas usted mismo en jsfiddle para ver los resultados.

  1. Usando un TreeWalker
  2. Recorrido iterativo personalizado
  3. Recorrido recursivo personalizado
  4. Consulta de XPath
  5. querySelectorAll
  6. getElementsByTagName

Supongamos por un momento que existe un método que le permite obtener todos los Textnodos de forma nativa. Aún tendría que atravesar cada nodo de texto resultante y llamar node.nodeValuepara obtener el texto real como lo haría con cualquier nodo DOM. Entonces, el problema del rendimiento no es iterar a través de los nodos de texto, sino a través de todos los nodos que no son texto y verificar su tipo. Yo diría (basado en los resultados) que TreeWalkerfunciona tan rápido como getElementsByTagName, si no más rápido (incluso con getElementsByTagName jugando con discapacidad).

Realicé cada prueba 1000 veces.

Método Total ms Promedio ms
--------------------------------------------------
document.TreeWalker 301 0.301
Recorrido iterativo 769 0,769
Traverser recursivo 7352 7.352
Consulta XPath 1849 1.849
querySelectorAll 1725 1.725
getElementsByTagName 212 0.212

Fuente para cada método:

TreeWalker

function nativeTreeWalker() {
    var walker = document.createTreeWalker(
        document.body, 
        NodeFilter.SHOW_TEXT, 
        null, 
        false
    );

    var node;
    var textNodes = [];

    while(node = walker.nextNode()) {
        textNodes.push(node.nodeValue);
    }
}

Recorrido de árbol recursivo

function customRecursiveTreeWalker() {
    var result = [];

    (function findTextNodes(current) {
        for(var i = 0; i < current.childNodes.length; i++) {
            var child = current.childNodes[i];
            if(child.nodeType == 3) {
                result.push(child.nodeValue);
            }
            else {
                findTextNodes(child);
            }
        }
    })(document.body);
}

Recorrido iterativo del árbol

function customIterativeTreeWalker() {
    var result = [];
    var root = document.body;

    var node = root.childNodes[0];
    while(node != null) {
        if(node.nodeType == 3) { /* Fixed a bug here. Thanks @theazureshadow */
            result.push(node.nodeValue);
        }

        if(node.hasChildNodes()) {
            node = node.firstChild;
        }
        else {
            while(node.nextSibling == null && node != root) {
                node = node.parentNode;
            }
            node = node.nextSibling;
        }
    }
}

querySelectorAll

function nativeSelector() {
    var elements = document.querySelectorAll("body, body *"); /* Fixed a bug here. Thanks @theazureshadow */
    var results = [];
    var child;
    for(var i = 0; i < elements.length; i++) {
        child = elements[i].childNodes[0];
        if(elements[i].hasChildNodes() && child.nodeType == 3) {
            results.push(child.nodeValue);
        }
    }
}

getElementsByTagName (desventaja)

function getElementsByTagName() {
    var elements = document.getElementsByTagName("p");
    var results = [];
    for(var i = 0; i < elements.length; i++) {
        results.push(elements[i].childNodes[0].nodeValue);
    }
}

XPath

function xpathSelector() {
    var xpathResult = document.evaluate(
        "//*/text()", 
        document, 
        null, 
        XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
        null
    );

    var results = [], res;
    while(res = xpathResult.iterateNext()) {
        results.push(res.nodeValue);  /* Fixed a bug here. Thanks @theazureshadow */
    }
}

Además, puede encontrar útil esta discusión: http://bytes.com/topic/javascript/answers/153239-how-do-i-get-elements-text-node

Anurag
fuente
1
Obtuve resultados mixtos para cada uno de los métodos anteriores en un navegador diferente; estos resultados anteriores son para Chrome. Firefox y Safari se comportan de manera muy diferente. Desafortunadamente, no tengo acceso a IE, pero podrían probarlos ustedes mismos en IE para ver si funciona. En cuanto a la optimización del navegador, no me preocuparía por elegir un método diferente para cada navegador siempre que las diferencias sean del orden de decenas de milisegundos o incluso de cientos.
Anurag
1
Esta es una respuesta realmente útil, pero tenga en cuenta que los diferentes métodos devuelven cosas muy diferentes. Muchos de ellos solo obtienen nodos de texto si son el primer hijo de su padre. Algunos de ellos solo pueden obtener el texto, mientras que otros pueden devolver nodos de texto reales con modificaciones menores. Hay un error en el recorrido del árbol iterativo que puede afectar su rendimiento. Cambiar node.nodeType = 3anode.nodeType == 3
theazureshadow
@theazureshadow - gracias por señalar el =error evidente . Lo arreglé, y la versión de xpath simplemente devolvía Textobjetos, y no la cadena real contenida en ella como lo estaban haciendo los otros métodos. El método que solo obtiene el texto del primer hijo es intencionalmente incorrecto, y lo mencioné al principio. Volveré a ejecutar las pruebas y publicaré los resultados actualizados aquí. Todas las pruebas (excepto getElementsByTagName y xpath) devuelven el mismo número de nodos de texto. XPath informa aproximadamente 20 nodos más que los demás, lo que ignoraré por ahora.
Anurag
6
Hice las pruebas equivalentes e hice un jsPerf: jsperf.com/text-node-traversal
Tim Down
1
@TimDown buen trabajo - que era prueba de discapacitados un ojo-dolor durante mucho tiempo :) Se debe añadir que como una respuesta ..
Anurag
5

Aquí hay una Iteratorversión moderna del método TreeWalker más rápido:

function getTextNodesIterator(el) { // Returns an iterable TreeWalker
    const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
    walker[Symbol.iterator] = () => ({
        next() {
            const value = walker.nextNode();
            return {value, done: !value};
        }
    });
    return walker;
}

Uso:

for (const textNode of getTextNodesIterator(document.body)) {
    console.log(textNode)
}

Versión más segura

El uso del iterador directamente puede atascarse si mueve los nodos mientras realiza un bucle. Esto es más seguro, devuelve una matriz:

function getTextNodes(el) { // Returns an array of Text nodes
    const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
    const nodes = [];
    while (walker.nextNode()) {
        nodes.push(walker.currentNode);
    }
    return nodes;
}
fregante
fuente
4

Sé que solicitó específicamente una colección, pero si lo decía de manera informal y no le importaba si estaban todos unidos en una cadena grande, puede usar:

var allTextAsString = document.documentElement.textContent || document.documentElement.innerText;

... siendo el primer elemento el enfoque estándar DOM3. Sin embargo, tenga en cuenta que innerTextparece excluir el contenido de la etiqueta de estilo o script en implementaciones que lo admiten (al menos IE y Chrome) mientras que los textContentincluye (en Firefox y Chrome).

Brett Zamir
fuente
1
Gracias, eso no es lo que quería. Mis necesidades exigen poder inspeccionarlos en el lugar como objetos DOM (como encontrar a sus padres, etc.)
levik
1
 document.deepText= function(hoo, fun){
        var A= [], tem;
        if(hoo){
            hoo= hoo.firstChild;
            while(hoo!= null){
                if(hoo.nodeType== 3){
                    if(typeof fun== 'function'){
                        tem= fun(hoo);
                        if(tem!= undefined) A[A.length]= tem;
                    }
                    else A[A.length]= hoo;
                }
                else A= A.concat(document.deepText(hoo, fun));
                hoo= hoo.nextSibling;
            }
        }
        return A;
    }

/ * Puede devolver una matriz de todos los nodos de texto descendientes de algún elemento padre, o puede pasarle alguna función y hacer algo (buscar o reemplazar o lo que sea) con el texto en su lugar.

Este ejemplo devuelve el texto de los nodos de texto que no son espacios en blanco en el cuerpo:

var A= document.deepText(document.body, function(t){
    var tem= t.data;
    return /\S/.test(tem)? tem: undefined;
});
alert(A.join('\n'))

* /

Útil para buscar y reemplazar, resaltar, etc.

Kennebec
fuente
1

Aquí hay una alternativa que es un poco más idiomática y (con suerte) más fácil de entender.

function getText(node) {
    // recurse into each child node
    if (node.hasChildNodes()) {
        node.childNodes.forEach(getText);
    }
    // get content of each non-empty text node
    else if (node.nodeType === Node.TEXT_NODE) {
        const text = node.textContent.trim();
        if (text) {
            console.log(text); // do something
        }
    }
}
jtschoonhoven
fuente
0
var el1 = document.childNodes[0]
function get(node,ob)
{
        ob = ob || {};

        if(node.childElementCount)
        {

            ob[node.nodeName] = {}
            ob[node.nodeName]["text"] = [];
            for(var x = 0; x < node.childNodes.length;x++)
            {   
                if(node.childNodes[x].nodeType == 3)
                {
                    var txt = node.childNodes[x].nodeValue;


                    ob[node.nodeName]["text"].push(txt)
                    continue
                }
                get(node.childNodes[x],ob[node.nodeName])       
            };  
        }
        else
        {
            ob[node.nodeName]   = (node.childNodes[0] == undefined ? null :node.childNodes[0].nodeValue )
        }
        return ob
}



var o = get(el1)
console.log(o)
Mankament Gra
fuente
0

después de que createTreeWalkeresté en desuso, puede usar

  /**
   * Get all text nodes under an element
   * @param {!Element} el
   * @return {Array<!Node>}
   */
  function getTextNodes(el) {
    const iterator = document.createNodeIterator(el, NodeFilter.SHOW_TEXT);
    const textNodes = [];
    let currentTextNode;
    while ((currentTextNode = iterator.nextNode())) {
      textNodes.push(currentTextNode);
    }
    return textNodes;
  }
Zuhair Taha
fuente