Javascript .querySelector найти <div> по innerTEXT

111

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

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

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

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

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

пароль
источник
Даже если бы вы могли это сделать, это было бы не быстрее, чем получить все div и отфильтровать их по свойству innerText. Так почему бы вам не сделать это вручную.
Реду
Возможный дубликат: собственный javascript-эквивалент селектора jQuery: contains ()
Шейди Алсет

Ответы:

102

Вопрос 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 и изменить его по своему усмотрению.

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

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

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];
Нильс
источник
2
Если вы хотите найти несколько элементов, замените их findна filter.
RubbelDieKatz
39

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

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

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

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

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

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

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

Лучше всего увидеть, есть ли у вас родительский элемент запрашиваемого 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");

Ну это все.

Уменьшить
источник
Это сработало для меня, но с innerHTML вместо innerText
Чейз Сандманн
5

Если вы не хотите использовать 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 как таковые, это будет захватывать родительский элемент текстового узла, который имеет текст, который вы ищете.

Стив Ботелло
источник
3

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

раскладка клавиатуры
источник
2

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

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

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

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

Мститель
источник
2

Используйте 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
}
Стивен Спунгин
источник
Можно ли использовать этот метод для добавления класса к элементу? напримерthisheading.setAttribute('class', "esubject")
Matthew
Если у вас есть элемент, конечно. Однако лучше использовать element.classList.add ("esubject"), хотя :)
Стивен Спунгин
1

Вот подход 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';
}
Ян Кю Пеблик
источник
0

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

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

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

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

}

Павел Зелиньски
источник
0

Здесь уже есть много отличных решений. Однако, чтобы предоставить более оптимизированное решение и еще одно, соответствующее идее поведения и синтаксиса 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');
    Это найдет все дивы , содержащие ссылку класс со словом идти в InnerText (например. Налево или идти вниз или идти прямо или это Go О.Д. )
  • document.queryInnerText('div.link', 'go');
    Это будет работать точно так же, как в приведенном выше примере, за исключением того, что вернет только первый соответствующий элемент.
  • document.queryInnerTextAll('a', /^Next$/);
    Найдите все ссылки с точным текстом Далее (с учетом регистра). Это исключит ссылки, содержащие слово Next вместе с другим текстом.
  • document.queryInnerText('a', /next/i);
    Найдите первую ссылку, содержащую слово « следующий» , независимо от регистра (например, « Следующая страница» или « Перейти к следующему» ).
  • e = document.querySelector('#page');
    e.queryInnerText('button', /Continue/);
    Это выполняет поиск в элементе контейнера кнопки, содержащей текст « Продолжить» (с учетом регистра). (например, " Продолжить" или " Перейти к следующему", но не продолжить )
b_laoshi
источник