Javascript .querySelector trouver <div> par innerTEXT

109

Comment puis-je trouver DIV avec certains textes? Par exemple:

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

Essayer d'utiliser quelque chose comme ça:

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

Mais bien sûr, cela ne fonctionnera pas. Comment puis-je le faire?

passwd
la source
Même si vous pouviez le faire, ce ne serait pas plus rapide que d'obtenir toutes les divs et de les filtrer sur la propriété innerText. Alors pourquoi ne pas le faire manuellement.
Rédu

Réponses:

100

La question d'OP concerne le JavaScript brut et non jQuery . Bien qu'il y ait beaucoup de réponses et que j'aime la réponse @Pawan Nogariya , veuillez consulter cette alternative.

Vous pouvez utiliser XPATH en JavaScript. Plus d'informations sur l'article MDN ici .

La document.evaluate()méthode évalue une requête / expression XPATH. Vous pouvez donc y passer des expressions XPATH, parcourir le document HTML et localiser l'élément souhaité.

Dans XPATH, vous pouvez sélectionner un élément, par le nœud de texte comme suit, qui obtient le divqui a le nœud de texte suivant.

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

Pour obtenir un élément contenant du texte, utilisez ce qui suit:

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

le contains() méthode dans XPATH prend un nœud comme premier paramètre et le texte à rechercher comme deuxième paramètre.

Vérifiez ce plunk ici , ceci est un exemple d'utilisation de XPATH en JavaScript

Voici un extrait de code:

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

Comme vous pouvez le voir, je peux saisir l'élément HTML et le modifier à ma guise.

gdyrrahite
la source
Je vous remercie! Fonctionne très bien! Mais comment "console.log" le "thisHeading.textContent" si j'ai besoin de saisir un seul mot de ce texte? Par exemple: '// div [contient (., \' / Vous vous connectez (. *) Fois cette session / \ ')]' puis alert (thisHeading.textContent. $ 1)
passwd
Ok, je le fais de cette façon:alert(thisHeading.textContent.replace(/.*You have login (.*) times.*/,'$1')) ;
passwd
@passwd, vous ne pouvez pas faire ça. Regex n'est pas pris en charge dans XPATH 1.0 (qui .evaluate()utilise. Veuillez me corriger si je me trompe), donc tout d'abord, vous ne pouvez pas rechercher quelque chose qui correspond à une expression régulière. Deuxièmement, la .textContentpropriété renvoie le nœud de texte de l'élément. Si vous voulez extraire une valeur de ce texte, vous devez la gérer explicitement, probablement en créant une sorte de fonction qui correspond à une expression régulière et renvoie la valeur correspondante dans le groupe. Pour cela, créez une nouvelle question sur un fil séparé.
gdyrrahitis
Internet Explorer: aucune prise en charge. Mais pris en charge dans Edge. Je ne sais pas ce que cela signifie, en termes de version.
Rolf
comment traiter une erreur au cas où l'élément que je recherche manque?
nenito
72

Vous pouvez utiliser cette solution assez simple:

Array.from(document.querySelectorAll('div'))
  .find(el => el.textContent === 'SomeText, text continues.');
  1. Le Array.fromconvertira la NodeList en tableau (il existe plusieurs méthodes pour le faire, comme l'opérateur de propagation ou la tranche)

  2. Le résultat étant maintenant un tableau permet d'utiliser la Array.findméthode, vous pouvez alors mettre n'importe quel prédicat. Vous pouvez également vérifier le textContent avec un regex ou ce que vous voulez.

Notez que Array.fromet Array.findsont des fonctionnalités ES2015. Te être compatible avec les anciens navigateurs comme IE10 sans transpilateur:

Array.prototype.slice.call(document.querySelectorAll('div'))
  .filter(function (el) {
    return el.textContent === 'SomeText, text continues.'
  })[0];
Niels
la source
2
Si vous souhaitez rechercher plusieurs éléments, remplacez-les findpar filter.
RubbelDieKatz
38

Depuis que vous l'avez demandé en javascript, vous pouvez avoir quelque chose comme ça

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

Et puis appelle ça comme ça

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
la source
1
On dirait que cela fonctionne, mais en retour, je n'obtiens que ceci:[object HTMLDivElement],[object HTMLDivElement]
passwd
Oui, vous obtiendrez les divs avec le texte correspondant et vous pourrez ensuite appeler la méthode de texte interne quelque chose comme ça foundDivs[0].innerText, aussi simple que ça
Pawan Nogariya
20

Cette solution effectue les opérations suivantes:

  • Utilise l'opérateur de diffusion ES6 pour convertir la liste de nœuds de tous les divs en tableau.

  • Fournit une sortie si le div contient la chaîne de requête, pas seulement si elle correspond exactement à la chaîne de requête (ce qui se produit pour certaines des autres réponses). par exemple, il doit fournir une sortie non seulement pour «SomeText» mais aussi pour «SomeText, le texte continue».

  • Sort le divcontenu entier , pas seulement la chaîne de requête. Par exemple, pour «SomeText, le texte continue», il doit afficher toute la chaîne, pas seulement «SomeText».

  • Permet à plusieurs divs de contenir la chaîne, pas seulement un seul 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
la source
3
J'aime cela. Propre, concis et compréhensible - tout en même temps.
ba_ul
2
Horriblement inefficace sûrement? Pensez à la taille innerHTMLde vos meilleurs <div>s. Vous devez d'abord filtrer les divs qui contiennent des enfants. Aussi suspect document.getElementsByTagName('div')peut être plus rapide, mais je ferais un benchmark pour être sûr.
Timmmm
C'est super pour moi, je peux mettre un bon sélecteur au début car je sais déjà que ça ne peut être que dans une table, cool, merci
gsalgadotoledo
10

Vous voyez mieux si vous avez un élément parent du div que vous interrogez. Si c'est le cas, récupérez l'élément parent et effectuez unelement.querySelectorAll("div") . Une fois que vous obtenez le, nodeListappliquez un filtre sur la innerTextpropriété. Supposons qu'un élément parent du div que nous interrogeons a un idof container. Vous pouvez normalement accéder au conteneur directement à partir de l'identifiant, mais faisons-le de la bonne manière.

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

Donc c'est tout.

Redu
la source
Cela a fonctionné pour moi mais avec innerHTML au lieu de innerText
Chase Sandmann
5

Si vous ne voulez pas utiliser jquery ou quelque chose comme ça, vous pouvez essayer ceci:

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

Une fois que vous avez les nœuds dans un tableau contenant le texte, vous pouvez faire quelque chose avec eux. Comme alerter chacun ou imprimer sur la console. Une mise en garde est que cela peut ne pas nécessairement saisir les div en soi, cela va saisir le parent du textnode qui contient le texte que vous recherchez.

Steve Botello
la source
3

Comme il n'y a pas de limite à la longueur du texte dans un attribut de données, utilisez des attributs de données! Et puis, vous pouvez utiliser des sélecteurs css réguliers pour sélectionner vos éléments comme le souhaite l'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>

Idéalement, vous effectuez la partie de configuration des attributs de données sur le chargement du document et affinez un peu le sélecteur querySelectorAll pour les performances.

keymap
la source
2

Google a cela comme un excellent résultat pour ceux qui ont besoin de trouver un nœud avec un certain texte. En guise de mise à jour, une liste de nœuds est désormais itérable dans les navigateurs modernes sans avoir à la convertir en tableau.

La solution peut utiliser forEach comme ça.

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

Cela a fonctionné pour moi pour faire un texte de recherche / remplacement dans une liste de nœuds lorsqu'un sélecteur normal ne pouvait pas choisir un seul nœud, j'ai donc dû filtrer chaque nœud un par un pour le vérifier pour l'aiguille.

Vigilante
la source
2

Utilisez XPath et document.evaluate (), et assurez-vous d'utiliser text () et non. pour l'argument contains (), ou bien vous aurez tout le HTML ou l'élément div le plus externe correspondant.

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

ou ignorez les espaces de début et de fin

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

ou correspondre à tous les types de balises (div, h1, p, etc.)

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

Puis itérer

let thisHeading;
while(thisHeading = headings.iterateNext()){
    // thisHeading contains matched node
}
Steven Spungin
la source
Cette méthode peut-elle être utilisée pour ajouter une classe à un élément? par exemplethisheading.setAttribute('class', "esubject")
Matthew
Une fois que vous avez l'élément, bien sûr. Cependant, il est préférable d'utiliser element.classList.add ("esubject") :)
Steven Spungin
1

Voici l'approche XPath mais avec un minimum de jargon XPath.

Sélection régulière basée sur les valeurs d'attribut d'élément (pour comparaison):

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

Sélection XPath basée sur le texte dans l'élément.

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

Et voici avec insensibilité à la casse puisque le texte est plus volatil:

// 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
la source
0

J'ai eu un problème similaire.

Fonction qui renvoie tous les éléments qui incluent le texte de arg.

Cela fonctionne pour moi:

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
la source
0

Il existe déjà de nombreuses solutions intéressantes ici. Cependant, pour fournir une solution plus rationalisée et une plus conforme à l'idée d'un comportement et d'une syntaxe de querySelector, j'ai opté pour une solution qui étend Object avec quelques fonctions prototypes. Ces deux fonctions utilisent des expressions régulières pour faire correspondre le texte, cependant, une chaîne peut être fournie en tant que paramètre de recherche libre.

Implémentez simplement les fonctions suivantes:

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

Avec ces fonctions implémentées, vous pouvez désormais passer des appels comme suit:

  • document.queryInnerTextAll('div.link', 'go');
    Ce trouverait tous les divs contenant le lien classe avec le mot aller dans le innerText (par exemple. Aller à gauche ou Aller en bas ou à droite aller ou il de Go od )
  • document.queryInnerText('div.link', 'go');
    Cela fonctionnerait exactement comme l'exemple ci-dessus, sauf que cela ne retournerait que le premier élément correspondant.
  • document.queryInnerTextAll('a', /^Next$/);
    Rechercher tous les liens contenant le texte exact Suivant (sensible à la casse). Cela exclura les liens contenant le mot Suivant ainsi que d'autres textes.
  • document.queryInnerText('a', /next/i);
    Recherchez le premier lien contenant le mot suivant , quelle que soit la casse (par exemple, Page suivante ou Aller à la suivante )
  • e = document.querySelector('#page');
    e.queryInnerText('button', /Continue/);
    Cela effectue une recherche dans un élément de conteneur pour un bouton contenant le texte, Continuer (sensible à la casse). (par exemple, Continuer ou Continuer au suivant mais pas continuer )
b_laoshi
la source