Как получить текстовый узел элемента? - PullRequest
80 голосов
/ 29 июня 2011
<div class="title">
   I am text node
   <a class="edit">Edit</a>
</div>

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

Ответы [ 9 ]

68 голосов
/ 29 июня 2011
var text = $(".title").contents().filter(function() {
  return this.nodeType == Node.TEXT_NODE;
}).text();

Получает contents выбранного элемента и применяет к нему функцию фильтра. Функция фильтра возвращает только текстовые узлы (т. Е. Эти узлы с nodeType == Node.TEXT_NODE).

51 голосов
/ 29 июня 2011

Вы можете получить nodeValue первого childNode, используя

$('.title')[0].childNodes[0].nodeValue

http://jsfiddle.net/TU4FB/

12 голосов
/ 29 июня 2011

Если вы хотите получить значение первого текстового узла в элементе, этот код будет работать:

var oDiv = document.getElementById("MyDiv");
var firstText = "";
for (var i = 0; i < oDiv.childNodes.length; i++) {
    var curNode = oDiv.childNodes[i];
    if (curNode.nodeName === "#text") {
        firstText = curNode.nodeValue;
        break;
    }
}

Вы можете увидеть это в действии здесь: http://jsfiddle.net/ZkjZJ/

11 голосов
/ 10 января 2016

Другое нативное решение JS, которое может быть полезно для «сложных» или глубоко вложенных элементов, - это использование NodeIterator . Поместите NodeFilter.SHOW_TEXT в качестве второго аргумента ("whatToShow") и выполните итерации только для дочерних элементов текстового узла элемента.

var root = document.getElementById('...'),
    iter = document.createNodeIterator (root, NodeFilter.SHOW_TEXT),
    textnode;

while (textnode = iter.nextNode()) {
  // do something with the text node
}

Вы также можете использовать TreeWalker. Разница между ними заключается в том, что NodeIterator - это простой линейный итератор, а TreeWalker позволяет вам также перемещаться с помощью братьев и сестер.

6 голосов
/ 15 мая 2018

Чистый JavaScript: минималистский

Прежде всего, всегда помните об этом при поиске текста в DOM.

MDN - пробелы в DOM

Эта проблема заставит вас обратить внимание на структуру вашего XML / HTML.

В этом чистом примере JavaScript я учитываю возможность нескольких текстовых узлов , которые могут чередоваться с другими видами узлов . Однако изначально я не делаю суждения о пробелах, оставляя эту задачу фильтрации другому коду.

В этой версии я передаю NodeList из кода вызова / клиента.

/**
* Gets strings from text nodes. Minimalist. Non-robust. Pre-test loop version.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @param nodeList The child nodes of a Node, as in node.childNodes.
* @param target A positive whole number >= 1
* @return String The text you targeted.
*/
function getText(nodeList, target)
{
    var trueTarget = target - 1,
        length = nodeList.length; // Because you may have many child nodes.

    for (var i = 0; i < length; i++) {
        if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
            return nodeList[i].nodeValue;  // Done! No need to keep going.
        }
    }

    return null;
}

Конечно, при первом тестировании node.hasChildNodes() не будет необходимости использовать цикл предварительного тестирования.

/**
* Gets strings from text nodes. Minimalist. Non-robust. Post-test loop version.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @param nodeList The child nodes of a Node, as in node.childNodes.
* @param target A positive whole number >= 1
* @return String The text you targeted.
*/
function getText(nodeList, target)
{
    var trueTarget = target - 1,
        length = nodeList.length,
        i = 0;

    do {
        if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
            return nodeList[i].nodeValue;  // Done! No need to keep going.
         }

        i++;
    } while (i < length);

    return null;
}

Чистый JavaScript: надежный

Здесь функция getTextById() использует две вспомогательные функции: getStringsFromChildren() и filterWhitespaceLines().


getStringsFromChildren ()

/**
* Collects strings from child text nodes.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @version 6.0
* @param parentNode An instance of the Node interface, such as an Element. object.
* @return Array of strings, or null.
* @throws TypeError if the parentNode is not a Node object.
*/
function getStringsFromChildren(parentNode)
{
    var strings = [],
        nodeList.
        length;

    if (!parentNode instanceof Node) {
        throw new TypeError("The parentNode parameter expects an instance of a Node.");
    }

    if (!parentNode.hasChildNodes()) {
        return null; // We are done. Node may resemble <element></element>
    }

    nodeList = parentNode.childNodes;
    length = nodeList.length;

    for (var i = 0; i < length; i++) {
        if (nodeList[i].nodeType === Node.TEXT_NODE) {
            strings.push(nodeList[i].nodeValue);
        }
    }

    if (strings.length > 0) {
        return strings;
    }

    return null;
}

filterWhitespaceLines ()

/**
* Filters an array of strings to remove whitespace lines.
* Generic, cross platform solution.
*
* @author Anthony Rutledge
* @version 6.0
* @param textArray a String associated with the id attribute of an Element.
* @return Array of strings that are not lines of whitespace, or null.
* @throws TypeError if the textArray param is not of type Array.
*/
function filterWhitespaceLines(textArray) 
{
    var filteredArray = [],
        whitespaceLine = /(?:^\s+$)/; // Non-capturing Regular Expression.

    if (!textArray instanceof Array) {
        throw new TypeError("The textArray parameter expects an instance of a Array.");
    }

    for (var i = 0; i < textArray.length; i++) {
        if (!whitespaceLine.test(textArray[i])) {  // If it is not a line of whitespace.
            filteredArray.push(textArray[i].trim());  // Trimming here is fine. 
        }
    }

    if (filteredArray.length > 0) {
        return filteredArray ; // Leave selecting and joining strings for a specific implementation. 
    }

    return null; // No text to return.
}

getTextById ()

/**
* Gets strings from text nodes. Robust.
* Generic, cross platform solution.
*
* @author Anthony Rutledge
* @version 6.0
* @param id A String associated with the id property of an Element.
* @return Array of strings, or null.
* @throws TypeError if the id param is not of type String.
* @throws TypeError if the id param cannot be used to find a node by id.
*/
function getTextById(id) 
{
    var textArray = null;             // The hopeful output.
    var idDatatype = typeof id;       // Only used in an TypeError message.
    var node;                         // The parent node being examined.

    try {
        if (idDatatype !== "string") {
            throw new TypeError("The id argument must be of type String! Got " + idDatatype);
        }

        node = document.getElementById(id);

        if (node === null) {
            throw new TypeError("No element found with the id: " + id);
        }

        textArray = getStringsFromChildren(node);

        if (textArray === null) {
            return null; // No text nodes found. Example: <element></element>
        }

        textArray = filterWhitespaceLines(textArray);

        if (textArray.length > 0) {
            return textArray; // Leave selecting and joining strings for a specific implementation. 
        }
    } catch (e) {
        console.log(e.message);
    }

    return null; // No text to return.
}

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

Пустые строки ("") не возвращены, потому что вам нужен текстовый узел, чтобы правильно указывать наличие действительного текста. Возвращение ("") может создать ложное впечатление о существовании текстового узла, что заставит кого-то предположить, что он может изменить текст, изменив значение .nodeValue. Это неверно, потому что текстовый узел не существует в случае пустой строки.

Пример 1 :

<p id="bio"></p> <!-- There is no text node here. Return null. -->

Пример 2 :

<p id="bio">

</p> <!-- There are at least two text nodes ("\n"), here. -->

Проблема возникает, когда вы хотите, чтобы ваш HTML был легко читаем, разнося его. Теперь, несмотря на то, что не существует читаемого человеком корректного текста, все еще есть текстовые узлы с символами новой строки ("\n") в их свойствах .nodeValue.

Люди видят примеры один и два как функционально эквивалентные - пустые элементы, ожидающие заполнения. DOM отличается от человеческого мышления. Вот почему функция getStringsFromChildren() должна определить, существуют ли текстовые узлы, и собрать значения .nodeValue в массив.

for (var i = 0; i < length; i++) {
    if (nodeList[i].nodeType === Node.TEXT_NODE) {
            textNodes.push(nodeList[i].nodeValue);
    }
}

Во втором примере два текстовых узла существуют, и getStringFromChildren() вернет .nodeValue их обоих ("\n"). Однако filterWhitespaceLines() использует регулярное выражение, чтобы отфильтровать строки чистых пробельных символов.

Возвращает ли null вместо символов новой строки ("\n") форму лжи клиенту / вызывающему коду? С человеческой точки зрения нет. С точки зрения DOM, да. Однако проблема здесь заключается в получении текста, а не его редактировании. Нет человеческого текста для возврата к вызывающему коду.

Никогда не знаешь, сколько символов новой строки может появиться в чьем-то HTML. Создание счетчика, который ищет «второго» символа новой строки, ненадежно. Может не существовать.

Конечно, еще дальше, проблема редактирования текста в пустом элементе <p></p> с дополнительным пробелом (пример 2) может означать уничтожение (возможно, пропуск) всего кроме одного текстового узла между теги абзаца, чтобы гарантировать, что элемент содержит именно то, что он должен отображать.

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

var whitespaceLine = /(?:^\s+$)/; // Non-capturing Regular Expression.

for (var i = 0; i < filteredTextArray.length; i++) {
    if (!whitespaceLine.test(textArray[i])) {  // If it is not a line of whitespace.
        filteredTextArray.push(textArray[i].trim());  // Trimming here is fine. 
    }
}

В этот момент вы можете получить вывод, похожий на этот:

["Dealing with text nodes is fun.", "Some people just use jQuery."]

Нет никакой гарантии, что эти две строки соседствуют друг с другом в DOM, поэтому объединение их с .join() может создать неестественную композицию. Вместо этого в коде, который вызывает getTextById(), вам нужно выбрать, с какой строкой вы хотите работать.

Проверьте вывод.

try {
    var strings = getTextById("bio");

    if (strings === null) {
        // Do something.
    } else if (strings.length === 1) {
        // Do something with strings[0]
    } else { // Could be another else if
        // Do something. It all depends on the context.
    }
} catch (e) {
    console.log(e.message);
}

Можно добавить .trim() внутри getStringsFromChildren(), чтобы избавиться от начальных и конечных пробелов (или превратить группу пробелов в строку нулевой длины (""), но как узнать априори, что каждыйПриложению может потребоваться произойти с текстом (строкой) после его обнаружения? Вы этого не сделаете, поэтому оставьте это для конкретной реализации, и пусть getStringsFromChildren() будет универсальным.

Могут быть случаи, когда этоуровень специфики (target и т. д.) не требуется. Это замечательно. В этих случаях используйте простое решение. Однако обобщенный алгоритм позволяет вам приспособиться к простым и сложным ситуациям.

5 голосов
/ 29 июня 2011

.text() - for jquery

$('.title').clone()    //clone the element
.children() //select all the children
.remove()   //remove all the children
.end()  //again go back to selected element
.text();    //get the text of element
4 голосов
/ 09 декабря 2016

версия ES6, которая возвращает содержимое первого узла #text

const extract = (node) => {
  const text = Array.from(node.childNodes).find(child => child.NodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}
2 голосов
/ 12 ноября 2013

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

var oDiv = document.getElementById("MyDiv");
var firstText = "";
for (var i = 0; i < oDiv.childNodes.length; i++) {
    var curNode = oDiv.childNodes[i];
    whitespace = /^\s*$/;
    if (curNode.nodeName === "#text" && !(whitespace.test(curNode.nodeValue))) {
        firstText = curNode.nodeValue;
        break;
    }
}

Проверьте это на jsfiddle: - http://jsfiddle.net/webx/ZhLep/

1 голос
/ 09 февраля 2016

Вы также можете использовать XPath text() тест узла, чтобы получить только текстовые узлы. Например

var target = document.querySelector('div.title');
var iter = document.evaluate('text()', target, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
var node;
var want = '';

while (node = iter.iterateNext()) {
    want += node.data;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...