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

时间:2011-06-29 11:52:15

标签: javascript jquery

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

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

10 个答案:

答案 0 :(得分:66)

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

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

答案 1 :(得分:49)

您可以使用

获取第一个childNode的nodeValue
$('.title')[0].childNodes[0].nodeValue

http://jsfiddle.net/TU4FB/

答案 2 :(得分:12)

如果您的意思是获取元素中第一个文本节点的值,则此代码将起作用:

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/

答案 3 :(得分:10)

另一个可用于“复杂”或深层嵌套元素的原生JS解决方案是使用NodeIterator。将<p id="visible">Not Hidden</p> <p>Change screen sizes!</p>作为第二个参数(“whatToShow”),并迭代元素的文本节点子节点。

@media (max-width: 1000px) {
  h1 {
    display: none;
  }
  #hidden {
    display: block;
  }
}
p {
  display: none;
}

您也可以使用<h1 id="heading">Heading</h1> <p id="hidden">Hidden</p>。两者之间的区别在于NodeFilter.SHOW_TEXT是一个简单的线性迭代器,而var root = document.getElementById('...'), iter = document.createNodeIterator (root, NodeFilter.SHOW_TEXT), textnode; while (textnode = iter.nextNode()) { // do something with the text node } 允许您通过兄弟和祖先进行导航。

答案 4 :(得分: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

答案 5 :(得分:5)

纯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(),不需要使用预测试循环。

/**
* 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 6.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;

    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;

    for (var i = 0; i < length; i++) {
        if (nodeList[i].nodeType === Node.TEXT_NODE) {
            strings.push(nodeList[i].nodeValue);
        }
    }

    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易于阅读时,问题就出现了。现在,即使没有人类可读的有效文本,仍有"\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中可能会出现多少个换行符。创建一个查找“第二个”换行符的计数器是不可靠的。它可能不存在。

当然,更进一步,在带有额外空格(示例2)的空<p></p>元素中编辑文本的问题可能意味着破坏(可能,跳过)除了一个以外的所有元素段落标记之间的文本节点,以确保元素包含它应该显示的内容。

无论如何,除了您正在做一些特别的事情之外,您还需要一种方法来确定哪个文本节点的.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等)。那样太好了。在这些情况下使用简单的解决方案。但是,通用算法使您能够适应简单和复杂的情况。

答案 6 :(得分:4)

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

const extract = (node) => {
  const text = Array.from(node.childNodes).find(child => child.NodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}

答案 7 :(得分: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/

答案 8 :(得分: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;
}

答案 9 :(得分:0)

这是我在ES6中的解决方案,创建一个与所有子节点(递归)的串联文本相矛盾的字符串。请注意,这也是访问子节点的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的解决方案。