Javascript .querySelector znajdź <div> za pomocą innerTEXT

109

Jak mogę znaleźć DIV z określonym tekstem? Na przykład:

<div>
SomeText, text continues.
</div>

Próbuję użyć czegoś takiego:

var text = document.querySelector('div[SomeText*]').innerTEXT;
alert(text);

Ale oczywiście to nie zadziała. Jak mogę to zrobić?

passwd
źródło
Nawet gdybyś mógł to zrobić, nie byłoby to szybsze niż pobranie wszystkich elementów div i przefiltrowanie ich przez właściwość innerText. Więc dlaczego nie zrobisz tego ręcznie.
Redu

Odpowiedzi:

100

Pytanie OP dotyczy zwykłego JavaScript, a nie jQuery . Chociaż odpowiedzi jest mnóstwo i podoba mi się odpowiedź @Pawan Nogariya , sprawdź tę alternatywę.

Możesz używać XPATH w JavaScript. Więcej informacji o artykule MDN tutaj .

document.evaluate()Sposób ocenia się zapytania xPath / ekspresji. Możesz więc przekazać tam wyrażenia XPATH, przejść do dokumentu HTML i zlokalizować żądany element.

W XPATH możesz wybrać element za pomocą węzła tekstowego, takiego jak poniżej, który otrzyma ten, divktóry ma następujący węzeł tekstowy.

//div[text()="Hello World"]

Aby uzyskać element, który zawiera tekst, użyj:

//div[contains(., 'Hello')]

Plik contains()Metoda w XPATH zajmuje węzeł jako pierwszy parametr i tekst do wyszukania jako drugi parametr.

Sprawdź ten plunk tutaj , to jest przykład użycia XPATH w JavaScript

Oto fragment kodu:

var headings = document.evaluate("//h1[contains(., 'Hello')]", document, null, XPathResult.ANY_TYPE, null );
var thisHeading = headings.iterateNext();

console.log(thisHeading); // Prints the html element in console
console.log(thisHeading.textContent); // prints the text content in console

thisHeading.innerHTML += "<br />Modified contents";  

Jak widać, mogę pobrać element HTML i zmodyfikować go tak, jak mi się podoba.

gdyrrahitis
źródło
Dziękuję Ci! Działa świetnie! Ale jak „console.log” do „thisHeading.textContent”, jeśli muszę pobrać tylko jedno słowo z tego tekstu? Na przykład: „// div [zawiera (., \ '/ You login (. *) Times this session / \')]”, a następnie alert (thisHeading.textContent. $ 1)
passwd
Ok, robię to w ten sposób:alert(thisHeading.textContent.replace(/.*You have login (.*) times.*/,'$1')) ;
passwd
@passwd, cóż, nie możesz tego zrobić. Regex nie jest obsługiwany w XPATH 1.0 (który .evaluate()używa. Proszę kogoś poprawić, jeśli się mylę), więc po pierwsze, nie możesz wyszukać czegoś, co pasuje do wyrażenia regularnego. Po drugie, .textContentwłaściwość zwraca węzeł tekstowy elementu. Jeśli chcesz pobrać wartość z tego tekstu, powinieneś obsłużyć to jawnie, prawdopodobnie tworząc jakąś funkcję, która pasuje do wyrażenia regularnego i zwraca pasującą wartość w grupie, aby utworzyć nowe pytanie w osobnym wątku.
gdyrrahitis
Internet Explorer: brak wsparcia. Ale obsługiwane w Edge. Nie jestem pewien, co to oznacza, biorąc pod uwagę wersję.
Rolf
jak postąpić z błędem w przypadku braku poszukiwanego elementu?
nenito
72

Możesz użyć tego całkiem prostego rozwiązania:

Array.from(document.querySelectorAll('div'))
  .find(el => el.textContent === 'SomeText, text continues.');
  1. Array.fromPrzekształci liście węzłów do tablicy (istnieją liczne sposoby wykonania tej jak operator do smarowania lub plaster)

  2. Rezultatem jest teraz tablica, która pozwala na użycie Array.findmetody, możesz następnie wstawić dowolny predykat. Możesz również sprawdzić textContent za pomocą wyrażenia regularnego lub cokolwiek chcesz.

Zwróć uwagę, że Array.fromi Array.findsą to funkcje ES2015. Są kompatybilne ze starszymi przeglądarkami, takimi jak IE10 bez transpilera:

Array.prototype.slice.call(document.querySelectorAll('div'))
  .filter(function (el) {
    return el.textContent === 'SomeText, text continues.'
  })[0];
Niels
źródło
2
Jeśli chcesz znaleźć wiele elementów, wymienić findsię filter.
RubbelDieKatz
38

Ponieważ poprosiłeś o to w javascript, więc możesz mieć coś takiego

function contains(selector, text) {
  var elements = document.querySelectorAll(selector);
  return Array.prototype.filter.call(elements, function(element){
    return RegExp(text).test(element.textContent);
  });
}

A potem nazwij to tak

contains('div', 'sometext'); // find "div" that contain "sometext"
contains('div', /^sometext/); // find "div" that start with "sometext"
contains('div', /sometext$/i); // find "div" that end with "sometext", case-insensitive
Pawan Nogariya
źródło
1
Niby to działa, ale w zamian dostaję tylko to:[object HTMLDivElement],[object HTMLDivElement]
passwd
Tak, będziesz otrzymywać elementy div z pasującym tekstem, a następnie możesz wywołać tam metodę tekstu wewnętrznego, coś takiego foundDivs[0].innerText, tak proste
Pawan Nogariya
20

To rozwiązanie wykonuje następujące czynności:

  • Używa operatora rozproszenia ES6 do konwersji NodeList wszystkich divs na tablicę.

  • Dostarcza dane wyjściowe, jeśli div zawiera ciąg zapytania, a nie tylko jeśli dokładnie jest równy ciągowi zapytania (co ma miejsce w przypadku niektórych innych odpowiedzi). np. Powinien dostarczyć dane wyjściowe nie tylko dla „Jakiś tekst”, ale także dla „Jakiś tekst, kontynuacja tekstu”.

  • Wyprowadza całą divzawartość, a nie tylko ciąg zapytania. np. dla „Jakiś tekst, tekst kontynuowany” powinien wypisać cały ciąg, a nie tylko „Jakiś tekst”.

  • Pozwala na wiele divznaków, aby zawierały ciąg, a nie tylko jeden div.

[...document.querySelectorAll('div')]      // get all the divs in an array
  .map(div => div.innerHTML)               // get their contents
  .filter(txt => txt.includes('SomeText')) // keep only those containing the query
  .forEach(txt => console.log(txt));       // output the entire contents of those
<div>SomeText, text continues.</div>
<div>Not in this div.</div>
<div>Here is more SomeText.</div>

Andrew Willems
źródło
3
Uwielbiam to. Czyste, zwięzłe i zrozumiałe - wszystko w tym samym czasie.
ba_ul,
2
Z pewnością strasznie nieefektywne? Pomyśl, jak duże innerHTMLsą Twoje najlepsze strony <div>. divNajpierw należy odfiltrować pliki zawierające dzieci. Podejrzany również document.getElementsByTagName('div')może być szybszy, ale dla pewności wykonałbym test porównawczy.
Timmmm
To dla mnie super, mogę ustawić dobry selektor na początek bo już wiem, że może być tylko w stole, super, dzięki
gsalgadotoledo
10

Najlepiej sprawdzić, czy masz element nadrzędny elementu div, o który pytasz. Jeśli tak, pobierz element nadrzędny i wykonaj polecenie element.querySelectorAll("div"). Po otrzymaniu nodeListzastosuj na nim filtr na innerTextwłaściwości. Załóżmy, że element nadrzędny elementu div, którego dotyczy zapytanie, ma wartość idof container. Zwykle możesz uzyskać dostęp do kontenera bezpośrednio z identyfikatora, ale zróbmy to we właściwy sposób.

var conty = document.getElementById("container"),
     divs = conty.querySelectorAll("div"),
    myDiv = [...divs].filter(e => e.innerText == "SomeText");

Więc to jest to.

Redu
źródło
To zadziałało dla mnie, ale z innerHTML zamiast innerText
Chase Sandmann
5

Jeśli nie chcesz używać jquery lub czegoś podobnego, możesz spróbować tego:

function findByText(rootElement, text){
    var filter = {
        acceptNode: function(node){
            // look for nodes that are text_nodes and include the following string.
            if(node.nodeType === document.TEXT_NODE && node.nodeValue.includes(text)){
                 return NodeFilter.FILTER_ACCEPT;
            }
            return NodeFilter.FILTER_REJECT;
        }
    }
    var nodes = [];
    var walker = document.createTreeWalker(rootElement, NodeFilter.SHOW_TEXT, filter, false);
    while(walker.nextNode()){
       //give me the element containing the node
       nodes.push(walker.currentNode.parentNode);
    }
    return nodes;
}

//call it like
var nodes = findByText(document.body,'SomeText');
//then do what you will with nodes[];
for(var i = 0; i < nodes.length; i++){ 
    //do something with nodes[i]
} 

Gdy masz już węzły w tablicy zawierającej tekst, możesz coś z nimi zrobić. Powiadom każdego lub wydrukuj na konsoli. Jedynym zastrzeżeniem jest to, że niekoniecznie musi to przechwytywać elementy div jako takie, spowoduje to przechwycenie rodzica węzła tekstowego, który zawiera tekst, którego szukasz.

Steve Botello
źródło
3

Ponieważ nie ma ograniczeń co do długości tekstu w atrybucie danych, użyj atrybutów danych! Następnie możesz użyć zwykłych selektorów css, aby wybrać element (y) tak, jak chce OP.

for (const element of document.querySelectorAll("*")) {
  element.dataset.myInnerText = element.innerText;
}

document.querySelector("*[data-my-inner-text='Different text.']").style.color="blue";
<div>SomeText, text continues.</div>
<div>Different text.</div>

Idealnie byłoby zrobić część ustawiania atrybutu danych podczas ładowania dokumentu i nieco zawęzić selektor querySelectorAll dla wydajności.

mapa klawiszy
źródło
2

Google ma to jako najlepszy wynik dla tych, którzy muszą znaleźć węzeł z określonym tekstem. W ramach aktualizacji lista węzłów jest teraz iterowalna w nowoczesnych przeglądarkach bez konieczności konwertowania jej na tablicę.

Rozwiązanie może używać forEach w ten sposób.

var elList = document.querySelectorAll(".some .selector");
elList.forEach(function(el) {
    if (el.innerHTML.indexOf("needle") !== -1) {
        // Do what you like with el
        // The needle is case sensitive
    }
});

Pomogło mi to w znalezieniu / zamianie tekstu na liście węzłów, gdy normalny selektor nie mógł wybrać tylko jednego węzła, więc musiałem filtrować każdy węzeł jeden po drugim, aby sprawdzić go pod kątem igły.

Samozwańczy stróż prawa
źródło
2

Użyj XPath i document.evaluate () i upewnij się, że używasz text (), a nie. dla argumentu zawiera (), w przeciwnym razie dopasowany zostanie cały kod HTML lub najbardziej zewnętrzny element div.

var headings = document.evaluate("//h1[contains(text(), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

lub zignoruj ​​początkowe i końcowe spacje

var headings = document.evaluate("//h1[contains(normalize-space(text()), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

lub dopasuj wszystkie typy tagów (div, h1, p itp.)

var headings = document.evaluate("//*[contains(text(), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

Następnie wykonaj iterację

let thisHeading;
while(thisHeading = headings.iterateNext()){
    // thisHeading contains matched node
}
Steven Spungin
źródło
Czy ta metoda może służyć do dodawania klasy do elementu? np.thisheading.setAttribute('class', "esubject")
Matthew
Kiedy już masz element, jasne. Jednak lepiej jest użyć element.classList.add („esubject”) :)
Steven Spungin,
1

Oto podejście XPath, ale z minimalnym żargonem XPath.

Zwykły wybór na podstawie wartości atrybutów elementu (do porównania):

// for matching <element class="foo bar baz">...</element> by 'bar'
var things = document.querySelectorAll('[class*="bar"]');
for (var i = 0; i < things.length; i++) {
    things[i].style.outline = '1px solid red';
}

Wybór XPath na podstawie tekstu w elemencie.

// for matching <element>foo bar baz</element> by 'bar'
var things = document.evaluate('//*[contains(text(),"bar")]',document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
for (var i = 0; i < things.snapshotLength; i++) {
    things.snapshotItem(i).style.outline = '1px solid red';
}

A oto z rozróżnianiem wielkości liter, ponieważ tekst jest bardziej zmienny:

// for matching <element>foo bar baz</element> by 'bar' case-insensitively
var things = document.evaluate('//*[contains(translate(text(),"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"),"bar")]',document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
for (var i = 0; i < things.snapshotLength; i++) {
    things.snapshotItem(i).style.outline = '1px solid red';
}
Jan Kyu Peblik
źródło
0

Miałem podobny problem.

Funkcja zwracająca wszystkie elementy zawierające tekst z arg.

To działa dla mnie:

function getElementsByText(document, str, tag = '*') {
return [...document.querySelectorAll(tag)]
    .filter(
        el => (el.text && el.text.includes(str))
            || (el.children.length === 0 && el.outerText && el.outerText.includes(str)))

}

Paweł Zieliński
źródło
0

Jest tu już wiele świetnych rozwiązań. Jednak, aby zapewnić bardziej usprawnione rozwiązanie i jeszcze jedno zgodne z ideą zachowania i składni querySelector, zdecydowałem się na rozwiązanie, które rozszerza Object o kilka funkcji prototypowych. Obie te funkcje używają wyrażeń regularnych do dopasowywania tekstu, jednak jako luźny parametr wyszukiwania można podać ciąg.

Wystarczy zaimplementować następujące funkcje:

// find all elements with inner text matching a given regular expression
// args: 
//      selector: string query selector to use for identifying elements on which we 
//                should check innerText
//      regex: A regular expression for matching innerText; if a string is provided,
//             a case-insensitive search is performed for any element containing the string.
Object.prototype.queryInnerTextAll = function(selector, regex) {
    if (typeof(regex) === 'string') regex = new RegExp(regex, 'i'); 
    const elements = [...this.querySelectorAll(selector)];
    const rtn = elements.filter((e)=>{
        return e.innerText.match(regex);
    });
    
    return rtn.length === 0 ? null : rtn
}

// find the first element with inner text matching a given regular expression
// args: 
//      selector: string query selector to use for identifying elements on which we 
//                should check innerText
//      regex: A regular expression for matching innerText; if a string is provided,
//             a case-insensitive search is performed for any element containing the string.
Object.prototype.queryInnerText = function(selector, text){
    return this.queryInnerTextAll(selector, text)[0];
}

Po zaimplementowaniu tych funkcji możesz teraz wykonywać połączenia w następujący sposób:

  • document.queryInnerTextAll('div.link', 'go');
    Spowoduje to znalezienie wszystkich elementów div zawierających klasę linków ze słowem go w tekście wewnętrznym (np. Idź w lewo lub GO w dół lub w prawo lub It's Go od )
  • document.queryInnerText('div.link', 'go');
    To działałoby dokładnie tak, jak w powyższym przykładzie, z tą różnicą, że zwróciłoby tylko pierwszy pasujący element.
  • document.queryInnerTextAll('a', /^Next$/);
    Znajdź wszystkie linki z dokładnym tekstem Dalej (z uwzględnieniem wielkości liter). Spowoduje to wykluczenie łączy zawierających słowo Dalej wraz z innym tekstem.
  • document.queryInnerText('a', /next/i);
    Znajdź pierwszy link, który zawiera słowo następne , niezależnie od wielkości liter (np. Następna strona lub Przejdź do następnej )
  • e = document.querySelector('#page');
    e.queryInnerText('button', /Continue/);
    Spowoduje to wyszukanie w elemencie kontenera przycisku zawierającego tekst Kontynuuj (z uwzględnieniem wielkości liter). (np. Kontynuuj lub Przejdź do następnego, ale nie kontynuuj )
b_laoshi
źródło