Как использовать Nokogiri и XPath для получения узлов с несколькими атрибутами

Я пытаюсь использовать Nokogiri для анализа HTML-файла с довольно эксцентричной разметкой. В частности, я пытаюсь захватить элементы div, в которых определены идентификаторы, несколько классов и стилей.

Разметка выглядит примерно так:

<div id="foo">
  <div id="bar" class="baz bang" style="display: block;">
    <h2>title</h2>
    <dl>
      List of stuff
    </dl>
  </div>
</div>

Я пытаюсь схватить <dl>, который находится внутри проблемы <div>. Я могу без проблем получить элементы div с одним атрибутом id, но не могу придумать, как заставить Nokogiri захватывать элементы div с классами id и.

Итак, они работают нормально:

content = @doc.xpath("//div[id='foo']")
content = @doc.css('div#foo')

Но они ничего не возвращают:

content = @doc.xpath("//div[id='bar']")
content = @doc.xpath("div#bar")

Есть ли что-то очевидное, что мне здесь не хватает?


person TimD    schedule 29.08.2010    source источник
comment
Хороший вопрос (+1). См. мой ответ для выражения XPath, которое выбирает требуемый <div> на основе комбинации значений его атрибутов.   -  person Dimitre Novatchev    schedule 29.08.2010


Ответы (5)


Я могу без проблем получить элементы div с одним атрибутом id, но не могу найти способ заставить Nokogiri захватывать элементы div как с идентификаторами, так и с классами.

Вы хотите:

//div[id='bar' and class='baz bang' and style='display: block;']
person Dimitre Novatchev    schedule 29.08.2010

Следующее работает для меня.

require 'rubygems'
require 'nokogiri'

html = %{
<div id="foo">
  <div id="bar" class="baz bang" style="display: block;">
    <h2>title</h2>
    <dl>
      List of stuff
    </dl>
  </div>
</div>
}

doc = Nokogiri::HTML.parse(html)
content = doc
  .xpath("//div[@id='foo']/div[@id='bar' and @class='baz bang']/dl")
  .inner_html

puts content
person AboutRuby    schedule 29.08.2010

Я думаю, что content = @doc.xpath("div#bar") это опечатка и должно быть content = @doc.css("div#bar") или лучше content = @doc.css("#bar"). Первое выражение во втором фрагменте кода кажется правильным.

person Daniel O'Hara    schedule 29.08.2010

Вы написали:

Я пытаюсь захватить div, у которых есть идентификаторы, несколько классов и стилей.

И

Я пытаюсь получить <dl>, который находится внутри проблемного div

Итак, этот XPath 1.0:

//div[@id][contains(normalize-space(@class),' ')][@style]/dl
person Community    schedule 30.08.2010

Я настоятельно рекомендую использовать в качестве отправной точки селекторы CSS, а не XPath, поскольку CSS более удобочитаем и с меньшей вероятностью приведет к визуальному шуму.

require 'nokogiri'

doc = Nokogiri::HTML(<<EOT)
<div id="foo">
  <div id="bar" class="baz bang" style="display: block;">
    <h2>title</h2>
    <dl>
      List of stuff
    </dl>
  </div>
</div>
EOT

Как только это будет проанализировано, используя CSS для поиска <div ... id="foo">:

doc.at('div#foo').to_html 
# => "<div id=\"foo\">\n" +
#    "  <div id=\"bar\" class=\"baz bang\" style=\"display: block;\">\n" +
#    "    <h2>title</h2>\n" +
#    "    <dl>\n" +
#    "      List of stuff\n" +
#    "    </dl>\n" +
#    "  </div>\n" +
#    "</div>"

И <div id="bar">:

doc.at('div#bar').to_html 
# => "<div id=\"bar\" class=\"baz bang\" style=\"display: block;\">\n" +
#    "    <h2>title</h2>\n" +
#    "    <dl>\n" +
#    "      List of stuff\n" +
#    "    </dl>\n" +
#    "  </div>"

Мы можем искать теги с именами обоих классов:


doc.at('.baz.bang').to_html
# => "<div id=\"bar\" class=\"baz bang\" style=\"display: block;\">\n" +
#    "    <h2>title</h2>\n" +
#    "    <dl>\n" +
#    "      List of stuff\n" +
#    "    </dl>\n" +
#    "  </div>"

И мы можем искать явный div с обоими классами и встроенным тегом <dl>:

doc.at('div.baz.bang dl').to_html
# => "<dl>\n" +
#    "      List of stuff\n" +
#    "    </dl>"

Или даже по ID и классам:

doc.at('div#bar.baz.bang').to_html
# => "<div id=\"bar\" class=\"baz bang\" style=\"display: block;\">\n" +
#    "    <h2>title</h2>\n" +
#    "    <dl>\n" +
#    "      List of stuff\n" +
#    "    </dl>\n" +
#    "  </div>"

И с <dl>:

doc.at('div#bar.baz.bang dl').to_html
# => "<dl>\n" +
#    "      List of stuff\n" +
#    "    </dl>"

Я использую at, что эквивалентно использованию search(...some selector...).first. Nokogiri поддерживает search, css и xpath, которые являются его вариантами CSS и XPath и возвращают NodeSet, а также at, at_css и at_xpath, которые возвращают Node. Важно понимать разницу между "NodeSet" и "Node" и как они связаны с text и Searchable, поэтому читайте документацию.

person the Tin Man    schedule 26.01.2020