【问题标题】:Using jQuery to gather all text nodes from a wrapped set, separated by spaces使用 jQuery 从一个包装集中收集所有文本节点,用空格分隔
【发布时间】:2025-02-18 00:10:02
【问题描述】:

我正在寻找一种方法来收集 jQuery 包装集中的所有文本,但我需要在它们之间没有文本节点的兄弟节点之间创建空间。

例如,考虑这个 HTML:

<div>
  <ul>
    <li>List item #1.</li><li>List item #2.</li><li>List item #3.</li>
  </ul>
</div>

如果我简单地使用jQuery的text()方法来收集&lt;div&gt;的文本内容,像这样:

var $div = $('div'), text = $div.text().trim();

alert(text);

产生以下文本:

列出项目#1.列出项目#2.列出项目#3。

因为每个&lt;li&gt; 元素之间没有空格。我真正要找的是这个(注意每个句子之间的单个空格):

列出项目#1。列出项目#3。列出项目 #3。

这表明我需要遍历包装集中的 DOM 节点,将每个节点的文本附加到一个字符串,后跟一个空格。我尝试了以下代码:

var $div = $('div'), text = '';

$div.find('*').each(function() {
  text += $(this).text().trim() + ' ';
});

alert(text);

但这产生了以下文本:

这是列表项#1。这是列表项#2。这是列表项#3。这是列表项#1。这是列表项#2。这是列表项#3。

我认为这是因为我正在遍历 &lt;div&gt; 的每个后代并附加文本,所以我在 &lt;ul&gt; 及其每个 &lt;li&gt; 子节点中获取文本节点,导致文本重复.

我想我可能会找到/编写一个普通的 JavaScript 函数来递归地遍历包装集的 DOM,收集和附加文本节点 - 但是有没有更简单的方法来使用 jQuery 来做到这一点?跨浏览器的一致性非常重要。

感谢您的帮助!

【问题讨论】:

    标签: javascript jquery dom traversal


    【解决方案1】:

    jQuery 主要处理元素,它的文本节点功能相对较弱。您可以使用contents() 获得所有孩子的列表,但您仍然需要检查类型,所以这与仅使用普通 DOM childNodes 没有什么不同。没有递归获取文本节点的方法,因此您必须自己编写一些东西,例如。类似:

    function collectTextNodes(element, texts) {
        for (var child= element.firstChild; child!==null; child= child.nextSibling) {
            if (child.nodeType===3)
                texts.push(child);
            else if (child.nodeType===1)
                collectTextNodes(child, texts);
        }
    }
    function getTextWithSpaces(element) {
        var texts= [];
        collectTextNodes(element, texts);
        for (var i= texts.length; i-->0;)
            texts[i]= texts[i].data;
        return texts.join(' ');
    }
    

    【讨论】:

    • 太棒了,这就像一个魅力!非常感谢,不知道没有你我该怎么办,bobince。 ;-)
    • &lt;span class="fwn fcg"&gt;&lt;span class="fcg"&gt;&lt;span class="fwb"&gt;&lt;a class="profileLink" href=""&gt;Ndumiso Mlilo&lt;/a&gt;&lt;a href=""&gt;&lt;/a&gt;&lt;/span&gt; added a new photo.&lt;/span&gt;&lt;/span&gt; 上工作得很好 ^_^
    【解决方案2】:

    这是我能想到的最简单的解决方案:

    $("body").find("*").contents().filter(function(){return this.nodeType!==1;});
    

    【讨论】:

    • 非常优雅的解决方案!
    • &lt;span class="fwn fcg"&gt;&lt;span class="fcg"&gt;&lt;span class="fwb"&gt;&lt;a class="profileLink" href=""&gt;Ndumiso Mlilo&lt;/a&gt;&lt;a href=""&gt;&lt;/a&gt;&lt;/span&gt; added a new photo.&lt;/span&gt;&lt;/span&gt; 上失败
    • 产生 added a new photo. Ndumiso Mlilo 而不是 Ndumiso Mlilo added a new photo.
    【解决方案3】:

    您可以使用 jQuery contents() 方法获取所有节点(包括文本节点),然后将您的集合过滤到仅文本节点。

    $("body").find("*").contents().filter(function(){return this.nodeType!==1;});
    

    您可以从那里创建您需要的任何结构。

    【讨论】:

    • 感谢您的回答,但这对我不起作用。您能否扩展您的示例代码?此外,我认为contents() 只遍历包装集的直接子代(参见api.jquery.com/contents),而不是所有后代。
    • @Bungle 没错,contents() 不会递归地收集节点,而只会收集直接的后代。
    • &lt;span class="fwn fcg"&gt;&lt;span class="fcg"&gt;&lt;span class="fwb"&gt;&lt;a class="profileLink" href=""&gt;Ndumiso Mlilo&lt;/a&gt;&lt;a href=""&gt;&lt;/a&gt;&lt;/span&gt; added a new photo.&lt;/span&gt;&lt;/span&gt; 失败
    • 产生 added a new photo. Ndumiso Mlilo 而不是 Ndumiso Mlilo added a new photo.
    【解决方案4】:

    我在@bobince 的出色答案的基础上构建了搜索工具,该工具将搜索表的所有列并过滤行以仅显示匹配(不区分大小写)用户的所有搜索词(以任何顺序提供)的那些。

    这是一个截图示例:

    这是我的 javascript/jQuery 代码:

    $(function orderFilter() {
      // recursively collect all text from child elements (returns void)
      function collectTextNodes(element, texts) {
        for (
          let child = element.firstChild;
          child !== null;
          child = child.nextSibling
        ) {
          if (child.nodeType === Node.TEXT_NODE) {
            texts.push(child);
          } else if (child.nodeType === Node.ELEMENT_NODE) {
            collectTextNodes(child, texts);
          }
        }
      }
    
      // separate all text from all children with single space
      function getAllText(element) {
        const texts = [];
        collectTextNodes(element, texts);
    
        for (let i = texts.length; i-- > 0; ) texts[i] = texts[i].data;
    
        return texts.join(' ').replace(/\s\s+/g, ' ');
      }
    
      // check to see if the search value appears anywhere in child text nodes
      function textMatchesFilter(tbody, searchVal) {
        const tbodyText = getAllText(tbody).toLowerCase();
        const terms = searchVal.toLowerCase().replace(/\s\s+/g, ' ').split(' ');
    
        return terms.every(searchTerm => tbodyText.includes(searchTerm));
      }
    
      // filter orders to only show those matching certain fields
      $(document).on('keyup search', 'input.js-filter-orders', evt => {
        const searchVal = $(evt.target).val();
        const $ordersTable = $('table.js-filterable-table');
        $ordersTable.find('tbody[hidden]').removeAttr('hidden');
    
        if (searchVal.length <= 1) return;
    
        // Auto-click the "Show more orders" button and reveal any collapsed rows
        $ordersTable
          .find('tfoot a.show-hide-link.collapsed, tbody.rotate-chevron.collapsed')
          .each((_idx, clickToShowMore) => {
            clickToShowMore.click();
          });
    
        // Set all tbodies to be hidden, then unhide those that match
        $ordersTable
          .find('tbody')
          .attr('hidden', '')
          .filter((_idx, tbody) => textMatchesFilter(tbody, searchVal))
          .removeAttr('hidden');
      });
    });
    

    就我们的目的而言,它可以完美运行!希望这对其他人有帮助!

    【讨论】:

      最近更新 更多