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

时间:2019-09-22 19:01:03

标签: javascript d3.js dom

我尝试将新的SVG元素添加到某些节点。为此,要添加元素的节点必须由包含在其中的文本内容中的字符串来找到,例如找到在 public void showToast(Context context, String message) { Toast.makeText(context,message,Toast.LENGTH_LONG).show(); } 标记内具有"id0"的任何节点。

以下是我的HTML层次结构示例:

<text>

我当然不知道正确的解决方案,但是我认为是这样的:

<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>,则返回到父节点并向其添加SVG元素。但是在行"id0"上发生了错误:

  

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

当我使用return this.parentElement;parentElement时也会发生类似的错误。

2 个答案:

答案 0 :(得分:2)

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

请注意,由于“ common HTML nodes and svg nodes belong to different namespaces”,您需要选择*[name()="svg"]之类的SVG节点。

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

有趣的xpath与CSS注释: What is the difference between cssSelector & Xpath and which is better with respect to performance for cross browser testing?

请注意,document.evaluate不支持IE

答案 1 :(得分:1)

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>