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

Gho*_*rus 5 javascript dom d3.js

我尝试向某些节点添加新的 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>
Run Code Online (Sandbox Code Playgroud)

我绝对不知道正确的解决方案,但我认为它是这样的:

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)
Run Code Online (Sandbox Code Playgroud)

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

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

当我使用parentElement或时会发生类似的错误parent

pti*_*tim 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)
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

返回的迭代器(/snaphots)对我来说是出乎意料的——肯定读过这个优秀的答案:/sf/answers/2272782431/,以及文档:MDN: document.evaluate

请注意,由于“普通 HTML 节点和 svg 节点属于不同的命名空间”,因此您需要选择 SVG 节点,例如*[name()="svg"].

关于查找文本,我建议使用contains(text(),'needle')而不是更明确的text()='needle',因为周围的任何空格needle都会导致选择器返回null.

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

请注意,IE 不支持document.evaluate


alt*_*lus 2

D3 中没有内置方法可以通过文本内容选择元素,这是因为 D3 内部使用Element.querySelector()Element.querySelectorAll()从 DOM 中选择元素。这些方法将CSS 选择器字符串作为单个参数,该参数由选择器级别 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.
Run Code Online (Sandbox Code Playgroud)

一旦有了这个节点,就可以轻松应用该元素所需的任何修改,即附加元素、设置属性……如果您不是在寻找唯一值而是在寻找匹配的多个元素,这也可以轻松适应处理多个节点同一个字符串。您只需返回迭代器找到的节点数组,然后可以将其直接传递到 D3.selectAll()以创建多个节点的选择。

对于一个有效的演示,请查看以下代码片段:

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.
Run Code Online (Sandbox Code Playgroud)
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());
Run Code Online (Sandbox Code Playgroud)