【问题标题】:How to get the text node of an element?如何获取元素的文本节点?
【发布时间】:2011-06-29 11:52:15
【问题描述】:
<div class="title">
   I am text node
   <a class="edit">Edit</a>
</div>

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

【问题讨论】:

标签: javascript jquery


【解决方案1】:
var text = $(".title").contents().filter(function() {
  return this.nodeType == Node.TEXT_NODE;
}).text();

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

【讨论】:

  • @Val - 抱歉,我错过了原始代码。我将更新答案以显示它。您需要text(),因为filter 函数返回节点本身,而不是节点的内容。
  • 不知道为什么,但我在测试上述理论时没有成功。我运行了以下 jQuery("*").each(function() { console.log(this.nodeType); }) 并得到了所有节点类型的 1
  • 是否可以在点击的节点上获取文本并在其所有子节点中获取文本?
  • 这很有趣并且解决了这个问题,但是当情况变得更复杂时会发生什么?有一种更灵活的方式来完成工作。
  • 没有 jQuery,document.querySelector(".title").childNodes[0].nodeValue
【解决方案2】:

您可以使用获取第一个 childNode 的 nodeValue

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

http://jsfiddle.net/TU4FB/

【讨论】:

  • 虽然这会起作用,但它取决于子节点的位置。如果(何时)发生变化,它将中断。
  • 如果文本节点不是第一个子节点,你可能会得到null作为返回值。
【解决方案3】:

另一个对“复杂”或深度嵌套元素有用的原生 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 也允许您通过兄弟姐妹和祖先进行导航。

【讨论】:

    【解决方案4】:

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

    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 可能,但有什么收获?
    • @ShadowWizard @Nilloc 推荐的方法是使用常量...curNode.nodeType == Node.TEXT_NODE(数字比较更快但 curNode.nodeType == 3 不可读 - 哪个节点的编号为 3?)
    • @ShadowWizard 使用curNode.NodeType === Node.TEXT_NODE。这种比较发生在一个未知的可能迭代循环中。比较两个小数字比比较不同长度的字符串(时间和空间考虑)要好。在这种情况下要问的正确问题是“我有什么类型/类型的节点?”,而不是“我有什么名字?” developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
    • @ShadowWizard 另外,如果你打算使用循环来筛选childNodes,要知道一个元素节点可以有多个文本节点。在通用解决方案中,可能需要指定要定位的元素节点中文本节点的哪个实例(第一个、第二个、第三个等...)。
    【解决方案5】:

    返回第一个#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) 如果一个nodeList中有多个文本节点,这里没有办法指定你想要哪个文本节点。您可能需要 first 文本节点,但您很可能需要 last 文本节点。
    • 你建议用什么来替换 Array.from ?
    • @Snowman 请为此类实质性更改添加您自己的答案,或向 OP 提出建议,让他们有机会将其纳入答案。
    • @jujule - 最好使用[...node.childNodes]HTMLCollection 转换为数组
    【解决方案6】:

    纯 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 中可能出现了多少换行符。创建一个查找“第二个”换行符的计数器是不可靠的。它可能不存在。

    当然,再往下说,编辑文本 在带有额外空格的空 &lt;p&gt;&lt;/p&gt; 元素中的问题(示例 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 等)。这太棒了。在这些情况下使用简单的解决方案。但是,通用算法可以让您适应简单和复杂的情况。

    【讨论】:

      【解决方案7】:

      .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
      

      【讨论】:

      • 我认为标准javascript的方法必须是'innerText'
      • 这不会像 OP 想要的那样工作 - 它也会在 a 元素中获取文本:jsfiddle.net/ekHJH
      • @James Allardice - 我已经完成了 jquery 解决方案,现在它可以工作了......
      • 这几乎可以工作,但是您在选择器的开头缺少.,这意味着您实际上得到了title元素的文本,而不是class="title"的元素
      • @reporter .innerText 是最近才采用的旧 IE 约定。就标准 DOM 脚本而言,node.nodeValue 是获取文本节点文本的方式。
      【解决方案8】:

      这也会忽略空格,因此,您永远不会使用核心 Javascript 获得空白 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 结构和文本排列方式。
      【解决方案9】:

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

      【讨论】:

        猜你喜欢
        • 2012-10-12
        • 1970-01-01
        • 1970-01-01
        • 2016-02-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-14
        • 2018-03-30
        相关资源
        最近更新 更多