如何通过文本内容查找DOM元素?

6

我尝试向一些节点添加新的SVG元素。为此,需要通过字符串在文本内容中查找要添加元素的节点,例如查找任何一个在<text>标签内包含"id0"的节点。

这是我的HTML层次结构示例:

<pre>
 <svg>
  <g>
   <g>
    <text> id3 </text>
    <text> 73% </text>
    <svg> ... </svg>
   </g>
   <g>
    <svg> ... </svg>
   </g>
   <g>
    <text> id0 </text>
    <text> 11% </text>
    <svg> ... </svg>
   </g>
   <g>
    <text> id1 </text>
    <text> 66% </text>
    <svg> ... </svg>
   </g>
   <g>
    <svg> ... </svg>
   </g>
  </g>
 </svg>
</pre>

我确实不知道正确的解决方案,但我认为应该是这样的:

d3.select('svg').select('g').selectAll('g').each(function (d, i) {})
                .select('g').select('text').filter(function () {
                  return (d3.select(this).text() === 'id0')
                })
                .select(function () {
                  return this.parentElement;
                })
                .append('svg')
                .attr('width', 400)
                .attr('height', 400)

如果标签<text>包含"id0",则返回父节点并向其添加一个SVG元素。但是在行return this.parentElement;处出现错误:

属性'parentElement'在类型'Window'上不存在。

当我使用parentElementparent时,类似的错误会发生。

你的文本内容很小,为什么不给组节点添加属性,例如a1="你的id",a2="你的%值",只要这些属性遵循有效的语法,它们可以是任何你喜欢的东西。 - SPlatten
2个回答

5
一种替代方法是xpath,它允许通过文本搜索:

// if you know there's only one...
const singleResult = document.evaluate(`//*[name()="text" and contains(text(), "id0")]`, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
console.log(singleResult.nodeName, singleResult.textContent)

// if there might be multiple results
const multipleResults = document.evaluate(`//*[name()="text" and contains(text(), "id_multiple")]`, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)

for (let i=0; i < multipleResults.snapshotLength; i++) {
  console.log(multipleResults.snapshotItem(i).nodeName, multipleResults.snapshotItem(i).textContent)
}
<svg>
    <g>
        <g>
            <text> id_multiple </text>
            <text> 73% </text>
            <svg></svg>
        </g>
        <g>
            <svg></svg>
        </g>
        <g>
            <text> id0 </text>
            <text> 11% </text>
            <svg></svg>
        </g>
        <g>
            <text> id_multiple </text>
            <text> 66% </text>
            <svg></svg>
        </g>
        <g>
            <svg></svg>
        </g>
    </g>
</svg>

迭代器(/snaphots)返回的结果对我来说不太符合预期-建议仔细阅读这个优秀答案:https://dev59.com/uI7ea4cB1Zd3GeqPHemR#32468320,以及文档:MDN: document.evaluate
请注意,由于“常见 HTML 节点和 SVG 节点属于不同的命名空间”,您需要选择类似这样的 SVG 节点:*[name()="svg"]
关于查找文本,我建议使用contains(text(),'needle')而不是更明确的text()='needle',因为needle周围的任何空格都会导致选择器返回null
有趣的 xpath 与 CSS 的评论:CSS 选择器和 XPath 有何区别?在跨浏览器测试性能方面哪个更好?
请注意,document.evaluate 不支持 IE。

1
哇,这真是个有趣的东西!一开始,我以为“不错,一个新的接口!”但当我意识到浏览器支持的广泛程度以及它已经存在了多久时,我感到有些遗憾我们没有更频繁地看到这种东西。我一定会记住它并将其添加到我的工具箱中。 - altocumulus
1
完全正确!当我今天在SO上找到相关答案时,我的反应也是一样的 - 这就是我理解它的方式 :) - ptim
我简直不敢相信这个已经存在了这么久,而我才刚刚发现它。真是一颗珍宝!现在我可以简化很多事情了。(说实话,可能要等到它们出问题了再用)。 - Nate

2
D3中没有一种内置的方法可以通过元素的文本内容选择元素,这是因为D3在内部使用Element.querySelector()Element.querySelectorAll()从DOM中选择元素。这些方法将CSS选择器字符串作为单个参数,该参数由Selectors Level 3规范定义。不幸的是,没有一种基于内容选择元素的方法(尽管曾经可以通过:contains()伪类来实现,但现在已经不存在了)。
因此,要构建您的选择,您必须通过将函数传递给 .select() 来进行选择,并返回您感兴趣的 <text> 元素。有多种方法可以做到这一点,但我想建议一个不太明显但优雅的方法。这利用了鲜为人知的 NodeIterator 接口,该接口可用于创建在满足过滤器条件的 DOM 节点列表上迭代的迭代器。 NodeIterator 实例是通过调用 Document.createNodeIterator() 创建的,它需要三个参数:
  1. 从DOM子树的根开始进行搜索。
  2. 一个位掩码,确定您感兴趣的节点类型;对于您的目的,这将是NodeFilter.SHOW_TEXT
  3. 一个实现.acceptNode()方法的对象,该方法是NodeFilter接口的一部分。此方法按文档顺序呈现指定类型的每个节点,并必须对任何匹配节点返回NodeFilter.FILTER_ACCEPT,对于任何其他节点返回NodeFilter.FILTER_REJECT。此时,您的实现将查找id值与实际文本元素的文本内容是否匹配。

然后,您可以在创建的节点迭代器上调用.nextNode()以遍历匹配节点列表。对于您的任务,这可能类似于以下内容:

document.createNodeIterator(
  this,                  // The root node of the searched DOM sub-tree.
  NodeFilter.SHOW_TEXT,  // Look for text nodes only.
  {
    acceptNode(node) {   // The filter method of interface NodeFilter
      return new RegExp(value).test(node.textContent)  // Check if text contains string
        ? NodeFilter.FILTER_ACCEPT                     // Found: accept node
        : NodeFilter.FILTER_REJECT;                    // Not found: reject and continue
  }
})
.nextNode()              // Get first node from iterator.
.parentElement;          // Found node is a "pure" text node, get parent <text> element.

一旦拥有了这个节点,就很容易对该元素进行任何修改--例如附加元素、设置属性等。如果您不是寻找唯一值,而是寻找与相同字符串匹配的多个元素,则可以轻松地适应处理多个节点。您只需返回由迭代器找到的节点数组,然后直接传递给D3的.selectAll()来创建多个节点的选择。
要查看工作演示,请查看以下代码片段:

function nodeIterator(value) {
  return function() {
    return document.createNodeIterator(
      this,                  // The root node of the searched DOM sub-tree.
      NodeFilter.SHOW_TEXT,  // Look for text nodes only.
      {
        acceptNode(node) {   // The filter method of interface NodeFilter
          return new RegExp(value).test(node.textContent)  // Check if text contains string
            ? NodeFilter.FILTER_ACCEPT                     // Found: accept node
            : NodeFilter.FILTER_REJECT;                    // Not found: reject and continue
      }
    })
    .nextNode()              // Get first node from iterator.
    .parentElement;          // Found node is a "pure" text node, get parent <text> element.
  }
}

const filter = nodeIterator("id0");
const sel = d3.select("svg").select(filter);

// Manipulate the selection:...
// sel.append("g")
//   .attr("transform", "...");

console.log(sel.node());
<script src="https://d3js.org/d3.v5.js"></script>

<svg>
  <g>
    <g>
      <text> id3 </text>
      <text> 73% </text>
      <svg></svg>
    </g>
    <g>
      <svg></svg>
    </g>
    <g>
      <text> id0 </text>
      <text> 11% </text>
      <svg></svg>
    </g>
    <g>
      <text> id1 </text>
      <text> 66% </text>
      <svg></svg>
    </g>
    <g>
      <svg></svg>
    </g>
  </g>
</svg>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接