getElementsByTagName () odpowiednik dla textNodes

79

Czy istnieje sposób na zebranie wszystkich textNodeobiektów w dokumencie?

getElementsByTagName()działa świetnie dla elementów, ale textNodenie są elementami.

Aktualizacja: zdaję sobie sprawę, że można to osiągnąć, chodząc po DOM - jak sugeruje wielu poniżej. Wiem, jak napisać funkcję DOM-walker, która sprawdza każdy węzeł w dokumencie. Miałem nadzieję, że jest na to sposób natywny dla przeglądarki. W końcu to trochę dziwne, że mogę uzyskać wszystkie komunikaty <input>za pomocą jednego wbudowanego wywołania, ale nie wszystkie textNode.

Levik
źródło

Odpowiedzi:

117

Aktualizacja :

Przedstawiłem kilka podstawowych testów wydajności dla każdej z tych 6 metod na 1000 uruchomień. getElementsByTagNamejest najszybszy, ale wykonuje półdupą robotę, ponieważ nie wybiera wszystkich elementów, a tylko jeden określony typ tagu (tak mi się wydaje p) i ślepo zakłada, że ​​jego firstChild jest elementem tekstowym. Może być trochę wadliwy, ale służy do celów demonstracyjnych i porównania jego wydajności z TreeWalker. Przeprowadź testy na jsfiddle, aby zobaczyć wyniki.

  1. Korzystanie z TreeWalker
  2. Niestandardowe przemierzanie iteracyjne
  3. Niestandardowe przemierzanie rekurencyjne
  4. Zapytanie XPath
  5. querySelectorAll
  6. getElementsByTagName

Załóżmy na chwilę, że istnieje metoda, która pozwala na Textnatywne pobranie wszystkich węzłów. Nadal musiałbyś przejść przez każdy wynikowy węzeł tekstowy i wywołać, node.nodeValueaby uzyskać rzeczywisty tekst, tak jak w przypadku każdego węzła DOM. Zatem kwestia wydajności nie polega na iterowaniu przez węzły tekstowe, ale na iterowaniu przez wszystkie węzły, które nie są tekstowe i sprawdzaniu ich typu. Twierdziłbym (na podstawie wyników), że TreeWalkerdziała tak samo szybko getElementsByTagName, jeśli nie szybciej (nawet z handicapped getElementsByTagName).

Uruchomiono każdy test 1000 razy.

Metoda Suma ms Średnia ms
--------------------------------------------------
document.TreeWalker 301 0,301
Iteracyjny Traverser 769 0,769
Rekurencyjny Traverser 7352 7.352
Zapytanie XPath 1849 1,849
querySelectorAll 1725 1.725.0
getElementsByTagName 212 0.212

Źródło dla każdej metody:

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

Rekurencyjne przechodzenie po drzewie

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

Iteracyjne przechodzenie po drzewie

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 (handicap)

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 */
    }
}

Również ta dyskusja może być pomocna - http://bytes.com/topic/javascript/answers/153239-how-do-i-get-elements-text-node

Anurag
źródło
1
Otrzymałem mieszane wyniki dla każdej z powyższych metod w innej przeglądarce - powyższe wyniki dotyczą przeglądarki Chrome. Firefox i Safari zachowują się zupełnie inaczej. Niestety, nie mam dostępu do IE, ale możecie sami przetestować w IE, czy działa. Jeśli chodzi o optymalizację przeglądarki, nie martwiłbym się wybieraniem innej metody dla każdej przeglądarki, o ile różnice są rzędu dziesiątek milisekund, a może nawet setek.
Anurag,
1
To naprawdę przydatna odpowiedź, ale uważaj, że różne metody zwracają bardzo różne rzeczy. Wiele z nich otrzymuje węzły tekstowe tylko wtedy, gdy są pierwszym dzieckiem swojego rodzica. Niektóre z nich mogą pobierać tylko tekst, podczas gdy inne mogą zwracać rzeczywiste węzły tekstowe z niewielkimi modyfikacjami. Wystąpił błąd w przechodzeniu po drzewie iteracyjnym, który może wpłynąć na jego wydajność. Zmień node.nodeType = 3nanode.nodeType == 3
theazureshadow
@theazureshadow - dzięki za wskazanie rażącego =błędu. Naprawiłem to, a wersja xpath po prostu zwracała Textobiekty, a nie rzeczywisty ciąg znaków w nim zawarty, tak jak robiły to inne metody. Metoda polegająca na pobieraniu tylko tekstu pierwszego dziecka jest celowo błędna, o czym wspomniałem na początku. Ponownie przeprowadzę testy i opublikuję zaktualizowane wyniki tutaj. Wszystkie testy (z wyjątkiem getElementsByTagName i xpath) zwracają taką samą liczbę węzłów tekstowych. XPath zgłasza około 20 węzłów więcej niż inne, które na razie zignoruję.
Anurag
6
Zrobiłem testy równoważne i stworzyłem jsPerf: jsperf.com/text-node-traversal
Tim Down
1
Dobra robota @TimDown - ten test z niepełnosprawnością męczył oczy przez długi czas :) Należy to dodać jako odpowiedź ..
Anurag
5

Oto nowoczesna Iteratorwersja najszybszej metody TreeWalker:

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

Stosowanie:

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

Bezpieczniejsza wersja

Bezpośrednie użycie iteratora może utknąć, jeśli przesuniesz węzły podczas wykonywania pętli. To jest bezpieczniejsze, zwraca tablicę:

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
źródło
4

Wiem, że specjalnie poprosiłeś o kolekcję, ale jeśli miałeś na myśli to nieformalnie i nie obchodziło Cię, czy wszystkie zostały połączone w jeden duży sznurek, możesz użyć:

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

... z pierwszym elementem jest podejście standardowe DOM3. Zwróć jednak uwagę, że innerTextwydaje się, że wyklucza zawartość skryptu lub tagu stylu w implementacjach, które je obsługują (przynajmniej IE i Chrome), podczas gdy textContentje zawiera (w przeglądarkach Firefox i Chrome).

Brett Zamir
źródło
1
Dzięki - nie tego chciałem. Moje potrzeby wymagają możliwości sprawdzenia ich na miejscu jako obiektów DOM (takich jak znalezienie ich rodziców itp.)
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;
    }

/ * Możesz zwrócić tablicę wszystkich podrzędnych węzłów tekstowych jakiegoś elementu nadrzędnego lub możesz przekazać mu jakąś funkcję i zrobić coś (znaleźć, zamienić lub cokolwiek) z tekstem w miejscu.

Ten przykład zwraca tekst węzłów tekstowych niebędących białymi znakami w treści:

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

* /

Przydatny do wyszukiwania i zastępowania, podświetlania i tak dalej

kennebec
źródło
1

Oto alternatywa, która jest nieco bardziej idiomatyczna i (miejmy nadzieję) łatwiejsza do zrozumienia.

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
źródło
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
źródło
0

po createTreeWalkerjest przestarzałe, możesz użyć

  /**
   * 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
źródło