<div class="title">
I am text node
<a class="edit">Edit</a>
</div>
Я хочу отримати тест "Я текстовий вузол", не хочу видаляти тег "редагувати", і мені потрібно рішення для перехресного браузера.
<div class="title">
I am text node
<a class="edit">Edit</a>
</div>
Я хочу отримати тест "Я текстовий вузол", не хочу видаляти тег "редагувати", і мені потрібно рішення для перехресного браузера.
Відповіді:
var text = $(".title").contents().filter(function() {
return this.nodeType == Node.TEXT_NODE;
}).text();
Це отримує contentsвибраний елемент і застосовує до нього функцію фільтра. Функція фільтра повертає лише текстові вузли (тобто ті вузли, з якими nodeType == Node.TEXT_NODE).
text()тому що filterфункція повертає самі вузли, а не вміст вузлів.
jQuery("*").each(function() { console.log(this.nodeType); })і отримав 1 для всіх типів вузлів.
Ви можете отримати nodeValue першого доменного вузла за допомогою
$('.title')[0].childNodes[0].nodeValue
nullповернене значення.
Якщо ви маєте на увазі отримати значення першого текстового вузла в елементі, цей код буде працювати:
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.
curNode.nodeType == Node.TEXT_NODE(числове порівняння швидше, але curNode.nodeType == 3 не читабельно - який вузол має число 3?)
curNode.NodeType === Node.TEXT_NODE. Це порівняння відбувається в циклі невідомих можливих ітерацій. Порівняння двох малих чисел краще, ніж порівняння рядків різної довжини (міркування часу та простору). Правильне питання, яке потрібно задати в цій ситуації, - це "який тип / тип вузла у мене?", А не "яке ім'я у мене?" developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
childNodes, знайте, що вузол елемента може мати більше одного текстового вузла. У загальному рішенні може знадобитися вказати, який екземпляр текстового вузла в вузлі елемента, на який потрібно націлити (перший, другий, третій тощо).
Ще одне нативне рішення 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цьому ви можете орієнтуватися і через братів і сестер, і предків.
По-перше, завжди пам’ятайте про це, шукаючи текст у 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;
}
Тут функція 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і подібний) не потрібен. Це чудово. У цих випадках використовуйте просте рішення. Однак узагальнений алгоритм дозволяє пристосувати прості та складні ситуації.
Версія ES6, яка повертає вміст першого вузла #text
const extract = (node) => {
const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
return text && text.textContent.trim();
}
.from()для створення дрібнокопійованого екземпляра масиву. (2) Використання .find()для порівняння рядків за допомогою .nodeName. Використання node.NodeType === Node.TEXT_NODEбуло б краще. (3) Повернення порожнього рядка, коли немає значення null, є більш істинним, якщо текстовий вузол не знайдено. Якщо текстовий вузол не знайдений, його може знадобитися створити! Якщо ви повернете порожній рядок, ""ви можете створити помилкове враження, що текстовий вузол існує і ним можна нормально керувати. По суті, повернення порожньої струни є білою брехнею і її краще уникати.
[...node.childNodes]для перетворення HTMLCollection в масиви
$('.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
aелемента: jsfiddle.net/ekHJH
.на початку свого селектора, це означає, що ви фактично отримуєте текст titleелемента, а не елементиclass="title"
.innerText- стара конвенція IE, прийнята нещодавно. З точки зору стандартного сценарію DOM - node.nodeValueце те, як людина захоплює текст текстового вузла.
Це також проігнорує пробіл, тому ви ніколи не отримали код 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 та розташування тексту там.
Ви також можете використовувати 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;
}
Це моє рішення в 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 .