Techioz Blog

Rails と Nokogiri を使用して直接の子とネストされていない子を見つけるにはどうすればよいですか?

概要

私は Rails 4.2.7 と Ruby (2.3) および Nokogiri を使用しています。ネストされたテーブルの子ではなく、テーブルの最も直接的な tr の子を見つけるにはどうすればよいですか?現在、テーブル内のテーブル行を次のように見つけています…

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

これは、テーブルの直接の行を検索するだけではありません。

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

ただし、行内の行も検索します。

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

直接の tr 要素のみを見つけるように検索を絞り込むにはどうすればよいですか?

解決策

XPath を使用すると、いくつかの手順でこれを実行できます。まずテーブルの「レベル」(つまり、他のテーブルでどのようにネストされているか) を見つけてから、同じ数のテーブルの祖先を持つすべての子孫 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 が他の trs を直接ネストしている場合、次のようなことができます (これは無効な 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

最初のステップは 2 つの別々のクエリに分けることができ、より明確になる可能性があります。

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

(ただし、trs のないテーブルがある場合、first_tr が nil になるため、これは失敗します。上記の結合 XPath は、その状況を正しく処理します。)