Javascript .querySelector find <div> por innerTEXT

109

Como posso encontrar DIV com determinado texto? Por exemplo:

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

Tentando usar algo assim:

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

Mas é claro que não vai funcionar. Como eu posso fazer isso?

senha
fonte
Mesmo se você pudesse fazer isso, não seria mais rápido do que obter todos os divs e filtrá-los pela propriedade innerText. Então, por que você não faz isso manualmente.
Redu

Respostas:

100

A pergunta do OP é sobre JavaScript puro e não jQuery . Embora haja uma abundância de respostas e eu como @Pawan Nogariya resposta , por favor, veja isto alternativa.

Você pode usar XPATH em JavaScript. Mais informações sobre o artigo MDN aqui .

O document.evaluate()método avalia uma consulta / expressão XPATH. Portanto, você pode passar expressões XPATH lá, ir para o documento HTML e localizar o elemento desejado.

No XPATH você pode selecionar um elemento, pelo nó de texto como o seguinte, que obtém o divque possui o nó de texto a seguir.

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

Para obter um elemento que contém algum texto, use o seguinte:

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

O contains()método em XPATH leva um nó como primeiro parâmetro e o texto a ser pesquisado como segundo parâmetro.

Verifique este plunk aqui , este é um exemplo de uso de XPATH em JavaScript

Aqui está um snippet de código:

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

Como você pode ver, posso pegar o elemento HTML e modificá-lo como quiser.

gdyrrahitis
fonte
Obrigado! Funciona bem! Mas como "console.log" o "thisHeading.textContent" se eu precisar pegar apenas uma palavra deste texto? Por exemplo: '// div [contém (., \' / Você faz o login (. *) Vezes esta sessão / \ ')]' e, em seguida, alerta (thisHeading.textContent. $ 1)
passwd
Ok, eu faço desta forma:alert(thisHeading.textContent.replace(/.*You have login (.*) times.*/,'$1')) ;
passwd
@passwd, bem, você não pode fazer isso. Regex não é compatível com XPATH 1.0 (que .evaluate()usa. Por favor, alguém me corrija se eu estiver errado), então, em primeiro lugar, você não pode pesquisar por algo que corresponda a uma expressão regular. Em segundo lugar, a .textContentpropriedade retorna o nó de texto do elemento. Se você deseja obter um valor deste texto, deve tratá-lo explicitamente, provavelmente criando algum tipo de função que corresponda a uma regex e retorne o valor correspondente no grupo. Para isso, faça uma nova pergunta em um tópico separado.
gdyrrahitis
Internet Explorer: sem suporte. Mas com suporte no Edge. Não tenho certeza do que isso significa, em termos de versão.
Rolf
como deve ser tratado um erro caso falte o elemento que procuro?
nenito,
72

Você poderia usar esta solução bastante simples:

Array.from(document.querySelectorAll('div'))
  .find(el => el.textContent === 'SomeText, text continues.');
  1. O Array.fromirá converter a NodeList em uma matriz (existem vários métodos para fazer isso, como o operador de propagação ou fatia)

  2. O resultado agora sendo um array permite o uso do Array.findmétodo, você pode então colocar qualquer predicado. Você também pode verificar o textContent com um regex ou o que quiser.

Observe que Array.frome Array.findsão recursos do ES2015. Ser compatível com navegadores mais antigos como o IE10 sem um transpiler:

Array.prototype.slice.call(document.querySelectorAll('div'))
  .filter(function (el) {
    return el.textContent === 'SomeText, text continues.'
  })[0];
Niels
fonte
2
Se desejar encontrar vários elementos, substitua findpor filter.
RubbelDieKatz
38

Já que você perguntou em javascript, você pode ter algo assim

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

E então chame assim

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
fonte
1
Parece que funciona, mas em troca, estou recebendo apenas isto:[object HTMLDivElement],[object HTMLDivElement]
passwd
Sim, você obterá os divs com texto correspondente e poderá chamar esse método de texto interno algo assim foundDivs[0].innerText, tão simples
Pawan Nogariya
20

Esta solução faz o seguinte:

  • Usa o operador spread ES6 para converter a NodeList de todos os divs em uma matriz.

  • Fornece saída se div contiver a string de consulta, não apenas se for exatamente igual à string de consulta (o que acontece com algumas das outras respostas). Por exemplo, deve fornecer saída não apenas para 'AlgumTexto', mas também para 'AlgumTexto, o texto continua'.

  • Produz todo o divconteúdo, não apenas a string de consulta. por exemplo, para 'AlgumTexto, o texto continua', ele deve imprimir toda a string, não apenas 'AlgumTexto'.

  • Permite que vários divs contenham a string, não apenas um único 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
fonte
3
Eu amo isto. Limpo, conciso e compreensível - tudo ao mesmo tempo.
ba_ul
2
Horrivelmente ineficiente, certamente? Pense no tamanho innerHTMLdos seus programas principais <div>. Você deve filtrar os divs que contêm filhos primeiro. Também suspeito document.getElementsByTagName('div')pode ser mais rápido, mas eu faria um benchmark para ter certeza.
Timmmm
Isso é ótimo para mim, eu posso definir um seletor bom no começo porque eu já sei que só pode ser em uma mesa, legal, obrigado
gsalgadotoledo
10

É melhor você ver se tem um elemento pai do div que está consultando. Se for assim, obtenha o elemento pai e execute um element.querySelectorAll("div"). Depois de obter o, nodeListaplique um filtro sobre a innerTextpropriedade. Suponha que um elemento pai do div que estamos consultando tenha um idde container. Normalmente, você pode acessar o container diretamente do id, mas vamos fazer isso da maneira correta.

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

Então é isso.

Redu
fonte
Isso funcionou para mim, mas com innerHTML em vez de innerText
Chase Sandmann
5

Se você não quiser usar jquery ou algo parecido, você pode tentar isto:

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

Depois de ter os nós em uma matriz que contém o texto, você pode fazer algo com eles. Como alertar cada um ou imprimir no console. Uma advertência é que isso pode não necessariamente capturar divs por si só, isso irá capturar o pai do textnode que contém o texto que você está procurando.

Steve Botello
fonte
3

Como não há limites para o comprimento do texto em um atributo de dados, use atributos de dados! E então você pode usar seletores css regulares para selecionar seu (s) elemento (s) como o OP deseja.

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>

O ideal é que você faça a parte de configuração do atributo de dados no carregamento do documento e restrinja um pouco o seletor querySelectorAll para desempenho.

mapa de teclado
fonte
2

O Google tem isso como um resultado principal para quem precisa encontrar um nó com determinado texto. Por meio de atualização, uma lista de nós agora pode ser iterada em navegadores modernos, sem a necessidade de convertê-la em um array.

A solução pode usar o forEach dessa forma.

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

Isso funcionou para mim fazer um localizar / substituir texto dentro de uma lista de nós quando um seletor normal não podia escolher apenas um nó, então tive que filtrar cada nó um por um para verificar se havia agulha.

Vigilante
fonte
2

Use XPath e document.evaluate () e certifique-se de usar text () e não. para o argumento contains (), ou então você terá o HTML inteiro, ou o elemento div mais externo correspondido.

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

ou ignore os espaços em branco à esquerda e à direita

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

ou corresponder a todos os tipos de tag (div, h1, p, etc.)

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

Em seguida, itere

let thisHeading;
while(thisHeading = headings.iterateNext()){
    // thisHeading contains matched node
}
Steven Spungin
fonte
Este método pode ser usado para adicionar uma classe a um elemento? por exemplothisheading.setAttribute('class', "esubject")
Mateus
Depois de ter o elemento, com certeza. No entanto, é melhor usar element.classList.add ("esubject") :)
Steven Spungin
1

Aqui está a abordagem XPath, mas com um mínimo de jargão XPath.

Seleção regular com base nos valores dos atributos do elemento (para comparação):

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

Seleção XPath com base no texto dentro do elemento.

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

E aqui está a não diferenciação de maiúsculas e minúsculas, já que o texto é mais volátil:

// 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
fonte
0

Eu tive um problema semelhante.

Função que retorna todos os elementos que incluem texto de arg.

Isso funciona para mim:

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
fonte
0

Já existem muitas soluções excelentes aqui. No entanto, para fornecer uma solução mais simplificada e mais consistente com a ideia de um comportamento e sintaxe querySelector, optei por uma solução que estende Object com algumas funções de protótipo. Ambas as funções usam expressões regulares para correspondência de texto, no entanto, uma string pode ser fornecida como um parâmetro de pesquisa flexível.

Simplesmente implemente as seguintes funções:

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

Com essas funções implementadas, agora você pode fazer chamadas da seguinte maneira:

  • document.queryInnerTextAll('div.link', 'go');
    Isso iria encontrar todos os divs contendo o link de classe com a palavra go na innerText (eg. Ir Esquerda ou ir para baixo ou ir para a direita ou E do Go od )
  • document.queryInnerText('div.link', 'go');
    Isso funcionaria exatamente como o exemplo acima, exceto que retornaria apenas o primeiro elemento correspondente.
  • document.queryInnerTextAll('a', /^Next$/);
    Encontre todos os links com o texto exato Próximo ( diferencia maiúsculas de minúsculas). Isso excluirá links que contenham a palavra Próximo junto com outro texto.
  • document.queryInnerText('a', /next/i);
    Encontre o primeiro link que contém a palavra seguinte , independentemente do caso (por exemplo, Próxima página ou Ir para a próxima )
  • e = document.querySelector('#page');
    e.queryInnerText('button', /Continue/);
    Isso executa uma pesquisa dentro de um elemento de contêiner para um botão que contém o texto Continue ( diferencia maiúsculas de minúsculas). (por exemplo, continuar ou continuar para o próximo, mas não continuar )
b_laoshi
fonte