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

问题描述 投票:0回答:3

我尝试向某些节点添加新的 SVG 元素。为此,要添加元素的节点必须通过文本内容中包含的字符串找到,例如找到

"id0"
标签内有
<text>
的任何节点。

这是我的 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;
线上出现错误:

类型“Window”上不存在属性“parentElement”。

当我使用

parentElement
parent
时,也会出现类似的错误。

javascript d3.js dom
3个回答
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://stackoverflow.com/a/32468320/2586761,以及文档:MDN: document.evaluate .

注意,因为“常见的 HTML 节点和 svg 节点属于不同的命名空间”,所以需要选择像

*[name()="svg"]
这样的 SVG 节点。

关于查找文本,我建议使用

contains(text(),'needle')
而不是更明确的
text()='needle'
,因为
needle
周围的任何空格都会导致选择器返回
null

有趣的 xpath 与 CSS 评论: cssSelector 和 Xpath 之间有什么区别,在跨浏览器测试性能方面哪个更好?

请注意,IE 不支持

document.evaluate


2
投票

D3 中没有内置方法通过文本内容选择元素,这是因为 D3 内部使用

Element.querySelector()
Element.querySelectorAll()
从 DOM 中选择元素。这些方法将 CSS Selector 字符串作为单个参数,该参数由 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>


0
投票

您可以使用 find-dom-nodes-by-content 库,它允许以简单的方式在 DOM 树中进行搜索。

const nodes = findDOMNodesByContent({ text: "Hello" }, document.body);

© www.soinside.com 2019 - 2024. All rights reserved.