Javascript .querySelector найти ‹div› по innerTEXT

Как мне найти DIV с определенным текстом? Например:

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

Пытаюсь использовать что-то вроде этого:

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

Но, конечно, не получится. Как я могу это сделать?


person passwd    schedule 08.05.2016    source источник
comment
Даже если бы вы могли это сделать, это было бы не быстрее, чем получить все div и отфильтровать их по свойству innerText. Так почему бы вам не сделать это вручную.   -  person Redu    schedule 08.05.2016


Ответы (14)


Вопрос OP касается простого JavaScript, а не jQuery. Хотя ответов много, и мне нравится ответ @Pawan Nogariya, пожалуйста, ознакомьтесь с этой альтернативой.

Вы можете использовать XPATH в JavaScript. Дополнительную информацию о статье MDN можно найти здесь.

Метод document.evaluate() оценивает запрос / выражение XPATH. Таким образом, вы можете передать туда выражения XPATH, перейти в документ HTML и найти нужный элемент.

В XPATH вы можете выбрать элемент по текстовому узлу, как показано ниже, который получает div, имеющий следующий текстовый узел.

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

Чтобы получить элемент, содержащий текст, используйте следующее:

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

Метод contains() в XPATH принимает узел в качестве первого параметра и текст для поиска в качестве второго параметра.

Проверьте это здесь, это пример использования XPATH в JavaScript.

Вот фрагмент кода:

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

Как видите, я могу взять HTML-элемент и изменить его по своему усмотрению.

person gdyrrahitis    schedule 08.05.2016
comment
Спасибо! Работает отлично! Но как в console.log thisHeading.textContent, если мне нужно взять только одно слово из этого текста? Например: '// div [содержит (., \' / Вы входите (. *) Раз в этот сеанс / \ ')]', а затем alert (thisHeading.textContent. $ 1) - person passwd; 08.05.2016
comment
Хорошо, я так делаю: alert(thisHeading.textContent.replace(/.*You have login (.*) times.*/,'$1')) ; - person passwd; 08.05.2016
comment
@passwd, ну ты не можешь этого сделать. Regex не поддерживается в XPATH 1.0 (который используется .evaluate(). Пожалуйста, поправьте меня, если я ошибаюсь), поэтому, во-первых, вы не можете искать что-то, что соответствует регулярному выражению. Во-вторых, свойство .textContent возвращает текстовый узел элемента. Если вы хотите получить значение из этого текста, вы должны обработать его явно, возможно, создав какую-то функцию, которая соответствует регулярному выражению и возвращает совпадающее значение в группе. Для этого создайте новый вопрос в отдельном потоке. - person gdyrrahitis; 08.05.2016
comment
Internet Explorer: нет поддержки. Но поддерживается в Edge. Я не уверен, что это значит с точки зрения версии. - person Rolf; 12.10.2017
comment
как обрабатывать ошибку, если элемент, который я ищу, отсутствует? - person nenito; 02.04.2018
comment
iterateNext () возвращает null, если элемент не был найден - person Pietro Coelho; 06.05.2021

Вы можете использовать это довольно простое решение:

Array.from(document.querySelectorAll('div'))
  .find(el => el.textContent === 'SomeText, text continues.');
  1. Array.from преобразует NodeList в массив (для этого есть несколько методов, таких как оператор распространения или срез)

  2. Результатом теперь является массив, позволяющий использовать метод Array.find, после чего вы можете вставить любой предикат. Вы также можете проверить textContent с помощью регулярного выражения или чего угодно.

Обратите внимание, что Array.from и Array.find являются функциями ES2015. Они должны быть совместимы со старыми браузерами, такими как IE10, без транспилятора:

Array.prototype.slice.call(document.querySelectorAll('div'))
  .filter(function (el) {
    return el.textContent === 'SomeText, text continues.'
  })[0];
person Niels    schedule 24.08.2017
comment
Если вы хотите найти несколько элементов, замените find на filter. - person RubbelDieKatz; 06.06.2020
comment
[].slice.call( ... ) еще проще ???? - person Oleg Mihailik; 29.05.2021

Поскольку вы спросили об этом в javascript, у вас может быть что-то вроде этого

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

А потом назовите это так

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
person Pawan Nogariya    schedule 08.05.2016
comment
Похоже, это работает, но взамен я получаю только следующее: [object HTMLDivElement],[object HTMLDivElement] - person passwd; 08.05.2016
comment
Да, вы получите div с соответствующим текстом в нем, а затем вы можете вызвать там метод внутреннего текста примерно так foundDivs[0].innerText, этот простой - person Pawan Nogariya; 09.05.2016

Это решение делает следующее:

  • Использует оператор распространения ES6 для преобразования списка узлов всех div в массив.

  • Предоставляет вывод, если div содержит строку запроса, а не только если она в точности равна строке запроса (что происходит с некоторыми другими ответами). например Он должен обеспечивать вывод не только для SomeText, но и для SomeText, текст продолжается.

  • Выводит все содержимое div, а не только строку запроса. например Для SomeText, текст продолжается, он должен выводить всю строку, а не только SomeText.

  • Позволяет нескольким div содержать строку, а не только одному 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>

person Andrew Willems    schedule 12.04.2018
comment
Мне это нравится. Чисто, лаконично и понятно - и все это одновременно. - person ba_ul; 10.12.2018
comment
Ужасно неэффективно? Подумайте, насколько велик innerHTML для ваших самых <div>. Вы должны сначала отфильтровать div, содержащие дочерние элементы. Также подозреваю, что document.getElementsByTagName('div') может быть быстрее, но я бы проверил, чтобы быть уверенным. - person Timmmm; 10.01.2020
comment
Это здорово для меня, я могу установить хороший селектор в начале, потому что я уже знаю, что он может быть только в таблице, круто, спасибо - person gsalgadotoledo; 13.03.2020

Лучше всего увидеть, есть ли у вас родительский элемент запрашиваемого div. Если да, получите родительский элемент и выполните element.querySelectorAll("div"). Как только вы получите nodeList, примените к нему фильтр над свойством innerText. Предположим, что родительский элемент div, который мы запрашиваем, имеет id из container. Обычно вы можете получить доступ к контейнеру напрямую из идентификатора, но давайте сделаем это правильно.

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

Итак, это все.

person Redu    schedule 08.05.2016
comment
Это сработало для меня, но с innerHTML вместо innerText - person Chase Sandmann; 28.09.2018

Если вы не хотите использовать jquery или что-то в этом роде, вы можете попробовать следующее:

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

Когда у вас есть узлы в массиве, содержащие текст, вы можете что-то с ними делать. Например, предупредить каждого или распечатать на консоли. Одно предостережение заключается в том, что это может не обязательно захватывать div как таковые, это будет захватывать родительский элемент текстового узла, который имеет текст, который вы ищете.

person Steve Botello    schedule 08.05.2016

Поскольку нет ограничений на длину текста в атрибуте данных, используйте атрибуты данных! И затем вы можете использовать обычные селекторы css для выбора ваших элементов, как того хочет 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>

В идеале вы выполняете часть настройки атрибутов данных при загрузке документа и немного сужаете селектор querySelectorAll для повышения производительности.

person keymap    schedule 25.02.2020

Столкнувшись с этим в 2021 году, я обнаружил, что использование XPATH слишком сложно (нужно научиться чему-то еще) для чего-то, что должно быть довольно простым.

Придумал это:

function querySelectorIncludesText (selector, text){
  return Array.from(document.querySelectorAll(selector))
    .find(el => el.textContent.includes(text));
}

Использование:

querySelectorIncludesText('button', 'Send')

Обратите внимание, что я решил использовать includes, а не строгое сравнение, потому что это то, что мне действительно нужно, не стесняйтесь адаптироваться.

Эти полифилы могут понадобиться, если вы хотите поддерживать все браузеры:

  /**
   * String.prototype.includes() polyfill
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
   * @see https://vanillajstoolkit.com/polyfills/stringincludes/
   */
  if (!String.prototype.includes) {
    String.prototype.includes = function (search, start) {
      'use strict';

      if (search instanceof RegExp) {
        throw TypeError('first argument must not be a RegExp');
      }
      if (start === undefined) {
        start = 0;
      }
      return this.indexOf(search, start) !== -1;
    };
  }
person Vadorequest    schedule 05.05.2021

У Google это лучший результат для тех, кому нужно найти узел с определенным текстом. Посредством обновления список узлов теперь можно повторять в современных браузерах без необходимости преобразовывать его в массив.

Решение может использовать forEach так.

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

Это помогло мне выполнить поиск / замену текста внутри списка узлов, когда обычный селектор не мог выбрать только один узел, поэтому мне пришлось фильтровать каждый узел один за другим, чтобы проверить его на иглу.

person Vigilante    schedule 06.11.2017

Используйте XPath и document.evaluate () и убедитесь, что вы используете text (), а не. для аргумента contains (), иначе у вас будет сопоставлен весь HTML или самый внешний элемент div.

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

или игнорировать начальные и конечные пробелы

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

или сопоставить все типы тегов (div, h1, p и т. д.)

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

Затем повторите

let thisHeading;
while(thisHeading = headings.iterateNext()){
    // thisHeading contains matched node
}
person Steven Spungin    schedule 11.12.2017
comment
Можно ли использовать этот метод для добавления класса к элементу? например thisheading.setAttribute('class', "esubject") - person Matthew; 19.12.2018
comment
Если у вас есть элемент, конечно. Однако лучше использовать element.classList.add (esubject), хотя :) - person Steven Spungin; 19.12.2018

Здесь уже есть много отличных решений. Однако, чтобы предоставить более оптимизированное решение и еще одно, соответствующее идее поведения и синтаксиса querySelector, я выбрал решение, которое расширяет Object парой функций-прототипов. Обе эти функции используют регулярные выражения для сопоставления текста, однако строка может быть предоставлена ​​как свободный параметр поиска.

Просто реализуйте следующие функции:

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

Теперь, когда эти функции реализованы, вы можете выполнять следующие вызовы:

  • document.queryInnerTextAll('div.link', 'go');
    В результате будут найдены все div, содержащие класс link со словом go в innerText (например, Go Left или вниз, иди вправо или Go od)
  • document.queryInnerText('div.link', 'go');
    Это будет работать точно так же, как в приведенном выше примере, за исключением того, что вернет только первый соответствующий элемент.
  • document.queryInnerTextAll('a', /^Next$/);
    Найдите все ссылки с точным текстом Далее (с учетом регистра). При этом будут исключены ссылки, содержащие слово Далее вместе с другим текстом.
  • document.queryInnerText('a', /next/i);
    Найдите первую ссылку, содержащую слово следующая, независимо от регистра (например, Следующая страница или Перейти к следующей).
  • e = document.querySelector('#page');
    e.queryInnerText('button', /Continue/);
    Выполняет поиск в элементе контейнера кнопки, содержащей текст Продолжить (с учетом регистра). (например, Продолжить или Перейти к следующему, но не продолжить)
person b_laoshi    schedule 31.07.2020

Вот подход XPath, но с минимумом жаргона XPath.

Обычный выбор по значениям атрибутов элемента (для сравнения):

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

Выбор XPath на основе текста внутри элемента.

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

А вот и нечувствительность к регистру, поскольку текст более изменчив:

// 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';
}
person Jan Kyu Peblik    schedule 22.08.2019

У меня была аналогичная проблема.

Функция, возвращающая все элементы, которые включают текст из аргумента.

Это работает для меня:

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

}

person Paweł Zieliński    schedule 27.10.2019

Я искал способ сделать что-то подобное с помощью Regex и решил создать что-то свое, чем хотел бы поделиться, если другие ищут подобное решение.

function getElementsByTextContent(tag, regex) {
  const results = Array.from(document.querySelectorAll(tag))
        .reduce((acc, el) => {
          if (el.textContent && el.textContent.match(regex) !== null) {
            acc.push(el);
          }
          return acc;
        }, []);
  return results;
}
person Morris Buel    schedule 06.06.2021