如何获取元素的文本节点?


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

我希望获得“我是文本节点”,不希望删除“ edit”标签,并且需要跨浏览器解决方案。


这个问题与stackoverflow.com/questions/3172166/…几乎完全相同-看到詹姆斯·答案的纯JS版本的答案
马拉,

Answers:


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

这将获取contents所选元素的,然后对其应用过滤功能。过滤器功能仅返回文本节点(即带有的节点nodeType == Node.TEXT_NODE)。


@Val-抱歉,我错过了原始代码。我将更新答案以显示它。您需要,text()因为filter函数返回节点本身,而不是节点的内容。
James Allardice 2011年

1
不知道为什么,但是在测试上述理论时我并不成功。我运行以下命令 jQuery("*").each(function() { console.log(this.nodeType); }),所有节点类型都得到1
Batandwa 2014年

是否可以在被单击的节点处获取文本,并在其所有子节点中获取文本?
詹娜·权

这很有趣并解决了这个问题,但是当情况变得更加复杂时会发生什么呢?有一种更灵活的方式来完成工作。
安东尼·鲁特里奇

如果没有jQuery,则document.querySelector(“。title”)。childNodes [0] .nodeValue
Balaji Gunasekaran


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 '17年

1
@Nilloc可能是什么,但是有什么收获呢?
暗影巫师为您耳边

5
@ShadowWizard @Nilloc推荐的方式是使用常量... curNode.nodeType == Node.TEXT_NODE(数字比较快,但curNode.nodeType == 3不可读-哪个节点的编号为3?)
mikep

1
@ShadowWizard使用curNode.NodeType === Node.TEXT_NODE。这种比较是在未知的可能迭代循环中进行的。比较两个小数字比比较各种长度的字符串(考虑时间和空间)要好。在这种情况下要问的正确问题是“我拥有哪种类型/类型的节点?”,而不是“我拥有什么名称?” developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType
Anthony Rutledge,

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

接下来,将返回值(数组或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);
    }
}

在例如两个,两个文本节点确实存在,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。太棒了。在这种情况下,请使用简单的解决方案。但是,通用算法使您能够适应简单和复杂的情况。


8

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

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)如果一个节点列表中有多个文本节点,则此处无法指定所需的文本节点。您可能需要第一个文本节点,但是您可能需要最后一个文本节点。
安东尼·鲁特里奇

您建议如何替换Array.from?
7:19

@Snowman请为此类实质性更改添加您自己的答案,或者为OP提出建议,以使他们有机会将其纳入他们的答案。
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
这并不能满足OP的要求-它也会在a元素中获取文本:jsfiddle.net/ekHJH
James Allardice 2011年

1
@James Allardice-我已经完成了jQuery解决方案,现在可以正常工作了.....
Pranay Rana 2011年

那几乎可以用,但是您.在选择器的开头缺少,这意味着您实际上得到的是title元素的文本,而不是class="title"
James Allardice 2011年

@reporter .innerText是最近才采用的旧IE约定。就标准DOM脚本而言,node.nodeValue就是如何获取文本节点的文本。
安东尼·鲁特里奇

2

这也将忽略空格,因此,您永远不会使用核心Javascript获得Blank textNodes..code。

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的结构和文字的排列摆布有英寸
安东尼拉特利奇

1

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

0

这是我在ES6中的解决方案,以创建一个与所有子节点(递归)的串联文本相矛盾的字符串。请注意,这也是访问childnodes的shdowroot。

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的解决方案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.