Як отримати текстовий вузол елемента?


98
<div class="title">
   I am text node
   <a class="edit">Edit</a>
</div>

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


це питання в значній мірі ідентично stackoverflow.com/questions/3172166/… - дивіться ці відповіді для простої версії JS від відповіді Джеймса
Мала

Відповіді:


79
var text = $(".title").contents().filter(function() {
  return this.nodeType == Node.TEXT_NODE;
}).text();

Це отримує contentsвибраний елемент і застосовує до нього функцію фільтра. Функція фільтра повертає лише текстові вузли (тобто ті вузли, з якими nodeType == Node.TEXT_NODE).


@Val - вибачте, я пропустив це з оригінального коду. Я оновлю відповідь, щоб її показати. Вам потрібно, text()тому що filterфункція повертає самі вузли, а не вміст вузлів.
Джеймс Еллардіс

1
Не знаю, чому, але я невдалий, коли перевіряю теорію вище. Я запустив наступне jQuery("*").each(function() { console.log(this.nodeType); })і отримав 1 для всіх типів вузлів.
Батандва

Чи можливо отримати текст у натиснутому вузлі та текст у всіх його дітей?
Дженна Квон

Це цікаво і вирішує цю проблему, але що відбувається, коли ситуація стає складнішою? Існує більш гнучкий спосіб виконати роботу.
Ентоні Рутлідж

Без jQuery, document.querySelector (". Title"). ChildNodes [0] .nodeValue
Баладжі Гюнасекаран

53

Ви можете отримати nodeValue першого доменного вузла за допомогою

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

http://jsfiddle.net/TU4FB/


4
Хоча це буде працювати, це залежить від положення дочірніх вузлів. Якщо (коли) це зміниться, воно порушиться.
Армстронгест

Якщо текстовий вузол не є першою дочірньою, ви можете отримати nullповернене значення.
Ентоні Рутлідж

14

Якщо ви маєте на увазі отримати значення першого текстового вузла в елементі, цей код буде працювати:

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/


Я думаю, ви можете використовувати і curNode.nodeType == 3замість цього nodeName.
Nilloc

1
@Nilloc, ймовірно, але який виграш?
Shadow Wizard is Ear For You

5
@ShadowWizard @Nilloc рекомендований спосіб для цього - використовувати константи ... curNode.nodeType == Node.TEXT_NODE(числове порівняння швидше, але curNode.nodeType == 3 не читабельно - який вузол має число 3?)
mikep

1
@ShadowWizard Використання curNode.NodeType === Node.TEXT_NODE. Це порівняння відбувається в циклі невідомих можливих ітерацій. Порівняння двох малих чисел краще, ніж порівняння рядків різної довжини (міркування часу та простору). Правильне питання, яке потрібно задати в цій ситуації, - це "який тип / тип вузла у мене?", А не "яке ім'я у мене?" developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
Ентоні Рутледж

2
@ShadowWizard Крім того, якщо ви збираєтеся використовувати цикл для просіювання childNodes, знайте, що вузол елемента може мати більше одного текстового вузла. У загальному рішенні може знадобитися вказати, який екземпляр текстового вузла в вузлі елемента, на який потрібно націлити (перший, другий, третій тощо).
Ентоні Рутлідж

13

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

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

// print all text nodes
while (textnode = iter.nextNode()) {
  console.log(textnode.textContent)
}
<p>
<br>some text<br>123
</p>

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


9

Чистий 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()спочатку тестуючи , не потрібно було б використовувати forцикл попереднього тестування .

/**
* 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 7.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,
        i = 0;

    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;

    do {
        if ((nodeList[i].nodeType === Node.TEXT_NODE)) {
            strings.push(nodeList[i].nodeValue);
         }

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

    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"символів newline ( ) є формою брехні клієнту / коду виклику? По-людськи, ні. З точки зору 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і подібний) не потрібен. Це чудово. У цих випадках використовуйте просте рішення. Однак узагальнений алгоритм дозволяє пристосувати прості та складні ситуації.


8

Версія ES6, яка повертає вміст першого вузла #text

const extract = (node) => {
  const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}

Мене цікавить ефективність та гнучкість. (1) Використання .from()для створення дрібнокопійованого екземпляра масиву. (2) Використання .find()для порівняння рядків за допомогою .nodeName. Використання node.NodeType === Node.TEXT_NODEбуло б краще. (3) Повернення порожнього рядка, коли немає значення null, є більш істинним, якщо текстовий вузол не знайдено. Якщо текстовий вузол не знайдений, його може знадобитися створити! Якщо ви повернете порожній рядок, ""ви можете створити помилкове враження, що текстовий вузол існує і ним можна нормально керувати. По суті, повернення порожньої струни є білою брехнею і її краще уникати.
Ентоні Рутлідж

(4) Якщо в nodeList є більше одного текстового вузла, тут не можна вказати, який саме текстовий вузол ви хочете. Можливо, ви хочете перший текстовий вузол, але ви дуже добре можете останнього текстового вузла.
Ентоні Рутлідж

Що ви пропонуєте замінити Array.from?
жужуль

@Snowman, будь ласка, додайте власну відповідь на такі змістовні зміни або викладіть рекомендації для ОП, щоб дати їм можливість включити їх у свою відповідь.
TylerH

@jujule - Краще використовувати [...node.childNodes]для перетворення HTMLCollection в масиви
vsync

5

.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

1
Я думаю, що метод для стандартного javascript повинен бути 'innerText'
Репортер

2
Це не працює так, як хоче ОП - він також отримає текст у межах aелемента: jsfiddle.net/ekHJH
James Allardice

1
@James Allardice - Я закінчую рішенням jquery, тепер це спрацює .................
Pranay Rana

Це майже вийде, але ви не вистачаєте .на початку свого селектора, це означає, що ви фактично отримуєте текст titleелемента, а не елементиclass="title"
James Allardice

@reporter .innerText- стара конвенція IE, прийнята нещодавно. З точки зору стандартного сценарію DOM - node.nodeValueце те, як людина захоплює текст текстового вузла.
Ентоні Рутлідж

2

Це також проігнорує пробіл, тому ви ніколи не отримали код Blank textNodes.. за допомогою основного 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/


curNode.nodeType === Node.TEXT_NODEбуло б краще. Використання порівняння рядків та регулярного вираження в циклі є низькопродуктивним рішенням, особливо, коли величина oDiv.childNodes.lengthзбільшується. Цей алгоритм вирішує специфічне питання ОП, але, можливо, за жахливу вартість продуктивності. Якщо розташування або кількість текстових вузлів зміниться, тоді це рішення не може бути гарантовано повернути точний вихід. Іншими словами, ви не можете націлити на потрібний текстовий вузол. Ви вподобані структуру HTML та розташування тексту там.
Ентоні Рутледж

1

Ви також можете використовувати text()тест вузла XPath, щоб отримати лише текстові вузли. Наприклад

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

0

Це моє рішення в ES6 до створити рядок, що суперечить зведеному тексту всіх дочірніх вузлів (рекурсивний) . Зауважте, що також відвідайте тіньову кору дочірніх вузлів.

function text_from(node) {
    const extract = (node) => [...node.childNodes].reduce(
        (acc, childnode) => [
            ...acc,
            childnode.nodeType === Node.TEXT_NODE ? childnode.textContent.trim() : '',
            ...extract(childnode),
            ...(childnode.shadowRoot ? extract(childnode.shadowRoot) : [])],
        []);

    return extract(node).filter(text => text.length).join('\n');
}

Це рішення було натхнене рішенням https://stackoverflow.com/a/41051238./1300775 .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.