How to get the text node of an element?

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

I wish to get the "I am text node", do not wish to remove the "edit" tag, and need a cross browser solution.

175107 次浏览

.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

您可以使用

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

Http://jsfiddle.net/tu4fb/

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

这将获取所选元素的 contents,并对其应用过滤器函数。Filter 函数只返回文本节点(即那些具有 nodeType == Node.TEXT_NODE的节点)。

如果您的意思是获取元素中第一个文本节点的值,这段代码可以工作:

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/

这也会忽略空格,因此,您永远不会得到使用核心 Javascript 的 Blank textNodes. . 代码。

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/

Another native JS solution that can be useful for "complex" or deeply nested elements is to use NodeIterator. Put NodeFilter.SHOW_TEXT as the second argument ("whatToShow"), and iterate over just the text node children of the element.

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也允许您通过兄弟姐妹和祖先进行导航。

您还可以使用 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;
}

返回第一个 # text 节点内容的 ES6版本

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

纯 JavaScript: 极简主义

首先,在 DOM 中查找文本时要始终记住这一点。

DOM 中的 MDN-空白

这个问题将使您关注 XML/HTML 的结构。

在这个纯 JavaScript 示例中,我说明了 多个文本节点的可能性可能是 与其他种类的节点交错。但是,最初,我不对空格进行判断,将过滤任务留给其他代码。

In this version, I pass a NodeList in from the calling / client code.

/**
* 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()


GetStringsFrom Children ()

/**
* 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)发送到应该处理它的客户机代码。希望数组中的字符串元素是真正的文本,而不是空白行。

返回空字符串("")是 没有,因为您需要一个文本节点来正确指示是否存在有效的文本。返回("")可能会给人一种文本节点存在的错误印象,导致某些人认为他们可以通过改变 .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);
}
}

在示例2中,确实存在两个文本节点,getStringFromChildren()将返回它们的 .nodeValue("\n")。但是,filterWhitespaceLines()使用正则表达式筛选出纯空白字符行。

返回 null而不是换行("\n")字符是否是对客户端/调用代码说谎的一种形式?用人类的话来说,不是。用 DOM 的术语来说,是的。但是,这里的问题是 收到短信,而不是编辑。没有返回到调用代码的人工文本。

人们永远不知道有多少换行符可能会出现在某人的 HTML 中。创建查找“第二个”换行符的计数器是不可靠的。它可能不存在。

当然,进一步来说,在一个带有额外空格的空 <p></p>元素中出现 editing text的问题(例子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);
}

我们可以在 getStringsFromChildren()内部添加 .trim()来去除前导和尾随空格(或者将一堆空格转换成零长度字符串("")) ,但是如何预先知道每个应用程序在找到文本(字符串)后可能需要发生什么?您不需要,因此将它留给一个特定的实现,并让 getStringsFromChildren()成为通用的。

有时可能不需要这种水平的特异性(target等)。太棒了。在这些情况下使用简单的解决方案。但是,通用算法使您能够适应简单和复杂的情况。

简单地通过 Vanilla JavaScript:

const el = document.querySelector('.title');
const text = el.firstChild.textContent.trim();

这里有一些过于复杂的解决方案,但操作非常简单,比如使用 .childNodes获取所有节点类型的子节点,使用 .filter提取 e.nodeType === Node.TEXT_NODE。或者,我们可能希望递归执行此操作和/或忽略“空”文本节点(所有空格)。

这些示例为了显示目的将节点转换为其文本内容,但是从技术上讲,这是与筛选分开的一个步骤。

const immediateTextNodes = el =>
[...el.childNodes].filter(e => e.nodeType === Node.TEXT_NODE);


const immediateNonEmptyTextNodes = el =>
[...el.childNodes].filter(e =>
e.nodeType === Node.TEXT_NODE && e.textContent.trim()
);


const firstImmediateTextNode = el =>
[...el.childNodes].find(e => e.nodeType === Node.TEXT_NODE);


const firstImmediateNonEmptyTextNode = el =>
[...el.childNodes].find(e =>
e.nodeType === Node.TEXT_NODE && e.textContent.trim()
);


// example usage:
const text = el => el.textContent;
const p = document.querySelector("p");
console.log(immediateTextNodes(p).map(text));
console.log(immediateNonEmptyTextNodes(p).map(text));
console.log(text(firstImmediateTextNode(p)));
console.log(text(firstImmediateNonEmptyTextNode(p)));


// if you want to trim whitespace:
console.log(immediateNonEmptyTextNodes(p).map(e => text(e).trim()));
<p>
<span>IGNORE</span>
<b>IGNORE</b>
foo
<br>
bar
</p>

返回文章页面 NodeIterator的递归替代译者:

const deepTextNodes = el => [...el.childNodes].flatMap(e =>
e.nodeType === Node.TEXT_NODE ? e : deepTextNodes(e)
);


const deepNonEmptyTextNodes = el =>
[...el.childNodes].flatMap(e =>
e.nodeType === Node.TEXT_NODE && e.textContent.trim()
? e : deepNonEmptyTextNodes(e)
);


// example usage:
const text = el => el.textContent;
const p = document.querySelector("p");
console.log(deepTextNodes(p).map(text));
console.log(deepNonEmptyTextNodes(p).map(text));
<p>
foo
<span>bar</span>
baz
<span><b>quux</b></span>
</p>

最后,如果您希望使用 .join(""),可以将文本节点数组加入到字符串中。但是,与修剪和文本内容提取一样,我可能不会把它放到核心过滤功能中,让调用者根据需要处理它。