如何使用 Rails 和 Nokogiri 查找直接子项而不是嵌套子项?

How do I find direct children and not nested children using Rails and Nokogiri?

我正在使用 Rails 4.2.7 与 Ruby (2.3) 和 Nokogiri。我如何找到 table 的最直接的 tr 子级而不是嵌套子级?目前我在 table 中找到 table 行,就像这样……

  tables = doc.css('table')
  tables.each do |table|
    rows = table.css('tr')

这不仅可以找到 table 的直接行,例如

<table>
    <tbody>
        <tr>…</tr>

但它也在行中查找行,例如

<table>
    <tbody>
        <tr>
            <td>
                <table>
                    <tr>This is found</tr>
                </table>
            </td>
        </tr>

如何优化搜索以仅查找直接 tr 元素?

不知道直接用css/xpath能不能实现,所以写了一个递归查找节点的小方法。一找到就停止递归。

xml= %q{
<root>
  <table>
    <tbody>
      <tr nested="false">
        <td>
          <table>
            <tr nested="true">
              This is found</tr>
          </table>
        </td>
      </tr>
    </tbody>
  </table>
  <another_table>
    <tr nested = "false">
      <tr nested = "true">
    </tr>
  </another_table>
  <tr nested = "false"/>
</root>
}

require 'nokogiri'

doc = Nokogiri::XML.parse(xml)

class Nokogiri::XML::Node
  def first_children_found(desired_node)
    if name == desired_node
      [self]
    else
      element_children.map{|child|
        child.first_children_found(desired_node)
      }.flatten
    end
  end
end

doc.first_children_found('tr').each do |tr|
  puts tr["nested"]
end

#=>
# false
# false
# false

您可以使用 XPath 分几步完成。首先,您需要找到 table 的“级别”(即它在其他 table 中的嵌套程度),然后找到具有相同数量 [=13] 的所有后代 tr =]祖先:

tables = doc.xpath('//table')
tables.each do |table|
  level = table.xpath('count(ancestor-or-self::table)')
  rows = table.xpath(".//tr[count(ancestor::table) = #{level}]")
  # do what you want with rows...
end

在更一般的情况下,您可能 tr 直接嵌套在其他 tr 中,您可以这样做(这将是无效的 HTML,但您可能有 XML 或其他一些标签):

tables.each do |table|
  # Find the first descendant tr, and determine its level. This
  # will be a "top-level" tr for this table. "level" here means how
  # many tr elements (including itself) are between it and the
  # document root.
  level = table.xpath("count(descendant::tr[1]/ancestor-or-self::tr)")
  # Now find all descendant trs that have that same level. Since
  # the table itself is at a fixed level, this means all these nodes
  # will be "top-level" rows for this table.
  rows = table.xpath(".//tr[count(ancestor-or-self::tr) = #{level}]")
  # handle rows...
end

第一步可以分解成两个单独的查询,这样可能更清楚:

first_tr = table.at_xpath(".//tr")
level = first_tr.xpath("count(ancestor-or-self::tr)")

(如果 table 没有 tr,这将失败,因为 first_tr 将是 nil。上面的组合 XPath 正确处理了这种情况.)