Javascript .querySelector buscar <div> por innerTEXT

108

¿Cómo puedo encontrar DIV con cierto texto? Por ejemplo:

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

Intentando usar algo como esto:

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

Pero, por supuesto, no funcionará. ¿Cómo puedo hacerlo?

passwd
fuente
Incluso si pudiera hacerlo, no sería más rápido que obtener todos los divs y filtrarlos sobre la propiedad innerText. Entonces, ¿por qué no lo hace manualmente?
Reducción

Respuestas:

99

La pregunta de OP es sobre JavaScript simple y no sobre jQuery . Aunque hay muchas respuestas y me gusta la respuesta de @Pawan Nogariya , consulte esta alternativa.

Puede utilizar XPATH en JavaScript. Más información sobre el artículo de MDN aquí .

El document.evaluate()método evalúa una consulta / expresión XPATH. Entonces puede pasar expresiones XPATH allí, atravesar el documento HTML y ubicar el elemento deseado.

En XPATH puede seleccionar un elemento, por el nodo de texto como el siguiente, que obtiene el divque tiene el siguiente nodo de texto.

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

Para obtener un elemento que contiene algo de texto, use lo siguiente:

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

El contains()método en XPATH toma un nodo como primer parámetro y el texto a buscar como segundo parámetro.

Compruebe este plunk aquí , este es un ejemplo de uso de XPATH en JavaScript

Aquí hay un fragmento 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 puede ver, puedo tomar el elemento HTML y modificarlo como quiera.

gdyrrahitis
fuente
¡Gracias! ¡Funciona genial! Pero, ¿cómo "console.log" el "thisHeading.textContent" si necesito tomar solo una palabra de este texto? Por ejemplo: '// div [contiene (., \' / Usted inicia sesión (. *) Veces esta sesión / \ ')]' y luego alerta (thisHeading.textContent. $ 1)
passwd
Ok, lo hago de esta manera:alert(thisHeading.textContent.replace(/.*You have login (.*) times.*/,'$1')) ;
passwd
@passwd, bueno, no puedes hacer eso. Regex no es compatible con XPATH 1.0 (que .evaluate()usa. Por favor, alguien me corrija si me equivoco), así que en primer lugar, no puede buscar algo que coincida con una expresión regular. En segundo lugar, la .textContentpropiedad devuelve el nodo de texto del elemento. Si desea tomar un valor de este texto, debe manejarlo explícitamente, probablemente creando algún tipo de función que coincida con una expresión regular y devuelva el valor coincidente en el grupo. Para eso, haga una nueva pregunta en un hilo separado.
gdyrrahitis
Internet Explorer: sin soporte. Pero compatible con Edge. No estoy seguro de lo que eso significa, en cuanto a versión.
Rolf
¿Cómo se debe manejar un error en caso de que falte el elemento que estoy buscando?
nenito
71

Podrías usar esta solución bastante simple:

Array.from(document.querySelectorAll('div'))
  .find(el => el.textContent === 'SomeText, text continues.');
  1. El Array.fromconvertirá el NodeList a una matriz (hay varios métodos para hacer esto como el operador de difusión o una rebanada)

  2. El resultado ahora es una matriz que permite usar el Array.findmétodo, luego puede colocar cualquier predicado. También puede verificar el textContent con una expresión regular o lo que quiera.

Tenga en cuenta que Array.fromy Array.findson características de ES2015. Ser compatible con navegadores más antiguos como IE10 sin un transpilador:

Array.prototype.slice.call(document.querySelectorAll('div'))
  .filter(function (el) {
    return el.textContent === 'SomeText, text continues.'
  })[0];
Niels
fuente
1
Si desea encontrar varios elementos, reemplácelos findcon filter.
RubbelDieKatz
38

Como lo ha pedido en javascript, puede tener algo como esto

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

Y luego llámalo así

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
fuente
1
Parece que esto funciona, pero a cambio solo [object HTMLDivElement],[object HTMLDivElement]
obtengo
Sí, obtendrá los divs con texto coincidente y luego puede llamar al método de texto interno algo como esto foundDivs[0].innerText, así de simple
Pawan Nogariya
20

Esta solución hace lo siguiente:

  • Utiliza el operador de propagación de ES6 para convertir la NodeList de todos los correos electrónicos diven una matriz.

  • Proporciona salida si div contiene la cadena de consulta, no solo si es exactamente igual a la cadena de consulta (lo que ocurre con algunas de las otras respuestas). Por ejemplo, debería proporcionar resultados no solo para 'SomeText' sino también para 'SomeText, el texto continúa'.

  • Muestra todo el divcontenido, no solo la cadena de consulta. Por ejemplo, para 'Algún texto, el texto continúa' debería generar esa cadena completa, no solo 'Algún texto'.

  • Permite que varios mensajes de correo divelectrónico contengan la cadena, no solo uno 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
fuente
3
Me encanta esto. Limpio, conciso y comprensible, todo al mismo tiempo.
ba_ul
2
Horriblemente ineficiente seguramente? Piense en lo grande que innerHTMLes para sus top-most <div>s. divPrimero debe filtrar los mensajes de correo electrónico que contengan hijos. También sospecho que document.getElementsByTagName('div')puede ser más rápido, pero lo compararía para estar seguro.
Timmmm
Esto es genial para mí, puedo configurar un buen selector al principio porque ya sé que solo puede estar en una mesa, genial, gracias
gsalgadotoledo
10

Es mejor que vea si tiene un elemento padre del div que está consultando. Si es así, obtenga el elemento padre y realice un element.querySelectorAll("div"). Una vez que obtenga el, nodeListaplique un filtro sobre la innerTextpropiedad. Suponga que un elemento padre del div que estamos consultando tiene un valor idde container. Normalmente puede acceder al contenedor directamente desde la identificación, pero hagámoslo de la manera correcta.

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

Eso es todo.

Redu
fuente
Esto funcionó para mí, pero con innerHTML en lugar de innerText
Chase Sandmann
5

Si no quieres usar jquery o algo así, puedes probar esto:

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

Una vez que tenga los nodos en una matriz que contenga el texto, puede hacer algo con ellos. Como alertar a cada uno o imprimir a consola. Una advertencia es que esto no necesariamente tomará divs per se, esto tomará el padre del textnode que tiene el texto que está buscando.

Steve Botello
fuente
3

Dado que no hay límites para la longitud del texto en un atributo de datos, ¡use atributos de datos! Y luego puede usar selectores css regulares para seleccionar su (s) elemento (s) como el OP quiere.

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>

Idealmente, realiza la parte de configuración de atributos de datos en la carga del documento y reduce un poco el selector querySelectorAll para mejorar el rendimiento.

mapa de teclas
fuente
2

Google tiene esto como un resultado superior para aquellos que necesitan encontrar un nodo con cierto texto. A modo de actualización, una lista de nodos ahora es iterable en los navegadores modernos sin tener que convertirla en una matriz.

La solución puede usar forEach así.

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

Esto funcionó para mí para buscar / reemplazar texto dentro de una lista de nodos cuando un selector normal no podía elegir solo un nodo, así que tuve que filtrar cada nodo uno por uno para verificar la aguja.

Vigilante
fuente
2

Use XPath y document.evaluate (), y asegúrese de usar text () y no. para el argumento contains (), o de lo contrario tendrá todo el HTML o el elemento div más externo coincidente.

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

o ignorar los espacios en blanco iniciales y finales

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

o coincidir con todos los tipos de etiquetas (div, h1, p, etc.)

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

Luego itera

let thisHeading;
while(thisHeading = headings.iterateNext()){
    // thisHeading contains matched node
}
Steven Spungin
fuente
¿Se puede usar este método para agregar una clase a un elemento? pthisheading.setAttribute('class', "esubject")
Mateo
Una vez que tenga el elemento, seguro. Sin embargo, es mejor usar element.classList.add ("esubject") aunque :)
Steven Spungin
1

Este es el enfoque XPath pero con un mínimo de jerga XPath.

Selección regular basada en valores de atributos de elementos (para comparación):

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

Selección XPath basada en texto dentro del 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';
}

Y aquí está la insensibilidad a mayúsculas y minúsculas, ya que el texto es más 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
fuente
0

Tuve un problema similar.

Función que devuelve todos los elementos que incluyen texto de arg.

Esto funciona para mi:

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

Aquí ya hay muchas soluciones geniales. Sin embargo, para brindar una solución más ágil y más acorde con la idea de un comportamiento y sintaxis de querySelector, opté por una solución que amplíe Object con un par de funciones prototipo. Ambas funciones utilizan expresiones regulares para hacer coincidir el texto, sin embargo, se puede proporcionar una cadena como parámetro de búsqueda flexible.

Simplemente implemente las siguientes funciones:

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

Con estas funciones implementadas, ahora puede realizar llamadas de la siguiente manera:

  • document.queryInnerTextAll('div.link', 'go');
    Esto encontraría todos los divs que contienen el enlace de clase con la palabra ir en el innerText (por ejemplo. Ir izquierda o ir hacia abajo o ir a la derecha o Es Go od )
  • document.queryInnerText('div.link', 'go');
    Esto funcionaría exactamente como en el ejemplo anterior, excepto que solo devolvería el primer elemento coincidente.
  • document.queryInnerTextAll('a', /^Next$/);
    Encuentre todos los enlaces con el texto exacto Siguiente (distingue entre mayúsculas y minúsculas). Esto excluirá los enlaces que contienen la palabra Siguiente junto con otro texto.
  • document.queryInnerText('a', /next/i);
    Busque el primer enlace que contiene la palabra siguiente , independientemente del caso (por ejemplo, Página siguiente o Ir a la siguiente )
  • e = document.querySelector('#page');
    e.queryInnerText('button', /Continue/);
    Esto realiza una búsqueda dentro de un elemento contenedor para un botón que contiene el texto, Continuar ( distingue entre mayúsculas y minúsculas). (p.ej. continuar o continuar con el siguiente pero no continuar )
b_laoshi
fuente