<div class="title">
I am text node
<a class="edit">Edit</a>
</div>
Я хочу получить "Я - текстовый узел", не хочу удалять тег "edit" и мне нужно кроссбраузерное решение.
<div class="title">
I am text node
<a class="edit">Edit</a>
</div>
Я хочу получить "Я - текстовый узел", не хочу удалять тег "edit" и мне нужно кроссбраузерное решение.
Ответы:
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.
}
Затем возвращаемое значение (Array или null) отправляется клиентскому коду, где оно должно быть обработано. Будем надеяться, что массив должен содержать строковые элементы реального текста, а не строки пробелов.
Пустые строки ( "") не возвращаются, потому что вам нужен текстовый узел, чтобы правильно указать наличие допустимого текста. Returning ( "") может создать ложное впечатление о существовании текстового узла, что заставит кого-то предположить, что он может изменить текст, изменив значение .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и тому подобное) не требуется. Это отлично. В таких случаях используйте простое решение. Однако обобщенный алгоритм позволяет учесть простые и сложные ситуации.
Версия 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..code с использованием основного 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увеличении величины . Этот алгоритм решает конкретный вопрос OP, но, возможно, с ужасной ценой производительности. Если расположение или количество текстовых узлов изменяется, то это решение не может гарантировать получение точного вывода. Другими словами, вы не можете настроить таргетинг на нужный текстовый узел. Вы находитесь во власти структуры 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 .