Nokogiri эквивалент метода jQuery closest() для поиска первого подходящего предка в дереве

#ruby #dom #nokogiri #closest #ancestor

#ruby #dom #nokogiri #ближайший #предок

Вопрос:

В jQuery есть прекрасный, хотя и несколько неправильно названный метод closest(), который проходит по дереву DOM в поисках подходящего элемента. Например, если у меня есть этот HTML:

 <table src="foo">
  <tr>
    <td>Yay</td>
  </tr>
</table>
  

Предполагая element , что установлено значение <td> , тогда я могу вычислить значение src следующим образом:

 element.closest('table')['src']
  

И это будет чисто возвращать «undefined», если отсутствует какой-либо элемент таблицы или его атрибут src.

Привыкнув к этому в Javascriptland, я хотел бы найти что-то эквивалентное Nokogiri в Rubyland, но самое близкое, что я смог придумать, — это явно неэлегантный хак с использованием ancestors():

 ancestors = element.ancestors('table')
src = ancestors.any? ? first['src'] : nil
  

Троичный необходим, потому что first возвращает nil, если вызывается для пустого массива. Лучшие идеи?

Ответ №1:

Вы можете вызвать first пустой массив, проблема в том, что он вернется nil , и вы не можете сказать nil['src'] , не расстроившись. Вы могли бы сделать это:

 src = (element.ancestors('table').first || { })['src']
  

И если вы работаете в Rails, вы могли бы использовать try таким образом:

 src = element.ancestors('table').first.try(:fetch, 'src')
  

Если вы часто занимаетесь подобными вещами, скройте уродство в методе:

 def closest_attr_from(e, selector, attr)
  a = e.closest(selector)
  a ? a[attr] : nil
end
  

и затем

 src = closest_attr_from(element, 'table', 'src')
  

Вы также можете исправить его прямо в Nokogiri :: XML::Node (но я бы не рекомендовал это):

 class Nokogiri::XML::Node
  def closest(selector)
    ancestors(selector).first
  end
  def closest_attr(selector, attr)
    a = closest(selector)
    a ? a[attr] : nil
  end
end
  

Комментарии:

1. Ответ принят для полной исчерпываемости — возможно, просто отправьте свой патч в Nokogiri в качестве запроса на извлечение!

2. Ruby 2.3 вводит оператор безопасной навигации , amp;. , поэтому вы можете сделать element.ancestors('table').firstamp;.src

Ответ №2:

Вы также можете сделать это с помощью xpath:

 element.xpath('./ancestor::table[1]')
  

Ответ №3:

Вам нужен src атрибут ближайшего предка таблицы, если он существует? Вместо того, чтобы получать элемент, который может существовать через XPath, а затем, возможно, получать атрибут через Ruby, запрашивайте атрибут непосредственно в XPath:

 ./ancestor::table[1]/@src
  

Вы получите либо атрибут, либо nil:

 irb(main):001:0> require 'nokogiri'
#=> true

irb(main):002:0> xml = '<r><a/><table src="foo"><tr><td /></tr></table></r>'
#=> "<r><a/><table src="foo"><tr><td /></tr></table></r>"

irb(main):003:0> doc = Nokogiri.XML(xml)
#=> #<Nokogiri::XML::Document:0x195f66c name="document" children=…

irb(main):004:0> doc.at('td').at_xpath( './ancestor::table[1]/@src' )
#=> #<Nokogiri::XML::Attr:0x195f1bc name="src" value="foo">

irb(main):005:0> doc.at('a').at_xpath( './ancestor::table[1]/@src' )
#=> nil