: селектор эквалайзера в нокогири


В jQuery у вас есть селектор :eq, чтобы уменьшить набор сопоставляемых элементов до одного с заданным индексом.

Как то же самое можно сделать с Нокогири? Я долго искал, но так и не нашел выхода.

См. этот пример:

require 'nokogiri'

html ='
<div>
<p>foo</p></div>
<span>
<p>bar</p>
<p>foobar</p></span>
'

doc = Nokogiri::HTML(html)

p doc.search('p:eq(0)')
# -> []
3 3

3 ответа:

Вот как я ловлю рыбу:

require 'nokogiri'

html ='
<p>foo</p>
<p>bar</p>
<p>foobar</p>
'

doc = Nokogiri::HTML(html)

doc.search('//p[3]')
=> [#<Nokogiri::XML::Element:0x811058ac name="p" children=[#<Nokogiri::XML::Text:0x81104f74 "foobar">]>]

Nokogiri возвратил NodeSet, набор узлов, который действует как массив. Я могу искать с помощью at вместо search, чтобы получить только узел:

doc.at('//p[3]').text
=> "foobar"

Или он может вернуть набор узлов, и Вы можете попросить Ruby извлечь из него конкретный элемент:

doc.search('//p')[2].text
=> "foobar"

p doc.search('p:eq(0)')

'p:eq(0)' это не CSS и не XPath. Это селектор jQuery, добавленный в JavaScript. Вы не можете использовать jQuery или JavaScript с Nokogiri в качестве метода доступа, это должен быть CSS или XPath.


Ваш пример даже не делает того, что я хочу. Оператор [i] сравним с N-м ребенком!

В соответствии с документацией jQuery для :eq:

Описание: уменьшите набор совпадающих элементов до одного с указанным индексом.

//p[3], или используя search('p')[2], Чтобы позволить Ruby разрезать NodeSet, вернет определенный узел. В моих примерах я извлекаю содержимое третьего тега <p>, который является эквивалент jQuery :eq(2).

Используя тот же пример, что и раньше:

require 'nokogiri'

html ='
<p>foo</p>
<p>bar</p>
<p>foobar</p>
'

doc = Nokogiri::HTML(html)

doc.search('//p[0]').text
doc.search('//p[1]').text
doc.search('//p[2]').text
doc.search('//p[3]').text

doc.search('//p')[0].text
doc.search('//p')[1].text
doc.search('//p')[2].text
doc.search('//p')[3].text

Сбрасывая это в IRB я вижу:

irb(main):011:0* doc.search('//p[0]').text # => ""
irb(main):012:0> doc.search('//p[1]').text # => "foo"
irb(main):013:0> doc.search('//p[2]').text # => "bar"
irb(main):014:0> doc.search('//p[3]').text # => "foobar"

irb(main):015:0> doc.search('//p')[0].text # => "foo"
irb(main):016:0> doc.search('//p')[1].text # => "bar"
irb(main):017:0> doc.search('//p')[2].text # => "foobar"
irb(main):018:0> doc.search('//p')[3].text 
NoMethodError: undefined method `text' for nil:NilClass
from (irb):18

Использование //p[1] эквивалентно использованию HTML ниже с JavaScript и jQuery, загрузке его в браузер и просмотру предупреждения, которое показывает "foo":

<html>
  <head>
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  </head>
  <body>
    <p>foo</p>
    <p>bar</p>
    <p>foobar</p>
    <script>
$().ready(function(){
    alert($('p:eq(0)').text());
});
    </script>
  </body>
</html>

Таким образом, //p с срезом [1] в XPath или [0] в Ruby эквивалентно :eq(0) в JavaScript. Но, поскольку Nokogiri не делает JavaScript или jQuery, вы должны использовать либо CSS, либо язык XPath.


Селекторы JQuery теперь доступны, что делает возможным следующее:

require 'nokogiri'

html = '
<html>
  <body>
    <p>foo</p>
    this text
    <p>bar</p>
  </body>
</html>
'

doc = Nokogiri::HTML(html)
doc.at('p:contains("foo")').next_sibling.text.strip
=> "this text"

Похоже, что единственный способ уменьшить набор согласованных узлов-это использовать Ruby и метод массива, подобный этому:

doc = Nokogiri ... 
doc.search("table")[2].search("...")

Ни XPath Нокогири, ни его CSS-селекторы не поддерживают функциональ-ность, равную оператору :eq jQuery.

Попробуйте с :nth-child(N) (Первый элемент находится с N = 1)