纯 JavaScript:极简主义
首先,在 DOM 中查找文本时始终牢记这一点。
MDN - Whitespace in the 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.
}
接下来,返回值(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 分隔开来使 HTML 易于阅读时,问题就出现了。现在,即使没有人类可读的有效文本,在其.nodeValue 属性中仍然存在带有换行符 ("\n") 字符的文本节点。
人类将示例 1 和示例 2 视为功能等价的 - 等待填充的空元素。 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);
}
可以在 getStringsFromChildren() 中添加 .trim() 以消除前导和尾随空格(或将一堆空格变成零长度字符串 (""),但你怎么能先验地知道每个应用程序可能需要在找到文本(字符串)后对其进行处理?您不需要,所以将其留给特定的实现,让getStringsFromChildren() 成为通用的。
有时可能不需要这种级别的特异性(target 等)。这太棒了。在这些情况下使用简单的解决方案。但是,通用算法可以让您适应简单和复杂的情况。