Нокогири: нужно превратить разметку, разделенную `hr`, в div

Данная разметка внутри HTML-документа выглядит так

<h3>test</h3>
<p>test</p>
<hr/>
<h3>test2</h3>
<p>test2</p>
<hr/>

Я хотел бы произвести это

<div>
  <h3>test</h3>
  <p>test</p>
</div>
<div>
  <h3>test2</h3>
  <p>test2</p>
</div>

Как лучше всего поступить с Нокогири?


person dan    schedule 25.01.2011    source источник
comment
Вы еще не приняли, не проголосовали и даже не прокомментировали ни один ответ. Разве мы еще не решили ваш вопрос удовлетворительно?   -  person Phrogz    schedule 27.01.2011
comment
Извините, у меня крайний срок, и у меня еще не было времени подтвердить решения. Я постараюсь добраться до него сегодня вечером!   -  person dan    schedule 27.01.2011


Ответы (3)


Правка: ответ переработан, чтобы сделать его немного чище.
Правка 2: небольшая переработка для сокращения на две строки.

require 'nokogiri'    
doc = Nokogiri::HTML <<ENDHTML
  <h3>test</h3>
  <p>test</p>
  <hr/>
  <h3>test2</h3>
  <p>test2</p>
  <hr/>
ENDHTML

body = doc.at_css('body')         # Created by parsing as HTML
kids = body.xpath('./*')          # Every child of the body
body.inner_html = ""              # Empty the body now that we have our nodes

div = (body << "<div>").first     # Create our first container in the body
kids.each do |node|               # For every child that was in the body...
  if node.name=='hr'              
    div = (body << '<div>').first # Create a new container for stuff
  else                            
    div << node                   # Move this into the last container
  end                             
end                               
div.remove unless div.child       # Get rid of a trailing, empty div

puts body.inner_html
#=> <div>
#=> <h3>test</h3>
#=> <p>test</p>
#=> </div>
#=> <div>
#=> <h3>test2</h3>
#=> <p>test2</p>
#=> </div>
person Phrogz    schedule 25.01.2011

Вот как бы я это сделал:

require 'nokogiri'

html = '
<h3>test</h3>
<p>test</p>
<hr/>
<h3>test2</h3>
<p>test2</p>
<hr/>
'

doc = Nokogiri::HTML(html)
doc2 = Nokogiri::HTML('<body />')
doc2_body = doc2.at('body')

doc.search('//h3 | //p').each_slice(2) do |ns| 
  nodeset = Nokogiri::XML::NodeSet.new(doc2, ns)
  div = Nokogiri::XML::Node.new('div', doc2)
  div.add_child(nodeset)
  doc2_body.add_child(div) 
end

puts doc2_body.inner_html

# >> <div>
# >> <h3>test</h3>
# >> <p>test</p>
# >> </div>
# >> <div>
# >> <h3>test2</h3>
# >> <p>test2</p>
# >> </div>
person the Tin Man    schedule 26.01.2011
comment
Вы предполагаете, что разметка — это только то, что показано в образце, по одному h3 и p на раздел, а не содержимое с общими разделителями hr. Вы можете быть правы, но это кажется довольно хрупким подходом, который сломается, когда какой-нибудь невинный редактор добавит второй абзац или что-то в этом роде. - person Phrogz; 26.01.2011
comment
Все, что мы можем сделать, это образец OP и запрошенный результат. Предположение, что ОП ничего не знает о проблеме, о которой он спрашивал, и пытается решить, кажется более серьезной проблемой. - person the Tin Man; 28.01.2011

Вот ответ, который использует Ruby 1.9.2 Enumerable#chunk для разделения дочерних элементов на разделы, а также упражнения класса NodeSet Нокогири:

require 'nokogiri'
doc = Nokogiri::HTML <<ENDHTML
  <h3>test</h3>
  <p>test</p>
  <hr/>
  <h3>test2</h3>
  <p>test2</p>
  <hr/>
ENDHTML

result = Nokogiri::XML::NodeSet.new( doc,
  doc.xpath('//body/*').chunk do |n|
    n.name=='hr'
  end.reject do |matched,nodes|
    matched
  end.map do |matched,nodes|
    doc.create_element('div').tap do |div|
      div << Nokogiri::XML::NodeSet.new( doc, nodes )
    end
  end )

puts result
#=> <div>
#=> <h3>test</h3>
#=> <p>test</p>
#=> </div>
#=> <div>
#=> <h3>test2</h3>
#=> <p>test2</p>
#=> </div>
person Phrogz    schedule 26.01.2011