Como navegar no DOM usando Nokogiri

votos
7

Eu estou tentando preencher as variáveis parent_element_h1e parent_element_h2. Alguém pode me ajudar a usar Nokogiri para obter a informação de que preciso para essas variáveis?

require 'rubygems'
require 'nokogiri'

value = Nokogiri::HTML.parse(<<-HTML_END)
  <html>
    <body>
      <p id='para-1'>A</p>
      <div class='block' id='X1'>
        <h1>Foo</h1>
        <p id='para-2'>B</p>
      </div>
      <p id='para-3'>C</p>
      <h2>Bar</h2>
      <p id='para-4'>D</p>
      <p id='para-5'>E</p>
      <div class='block' id='X2'>
        <p id='para-6'>F</p>
      </div>
    </body>
  </html>
HTML_END

parent = value.css('body').first

# start_here is given: A Nokogiri::XML::Element of the <div> with the id 'X2
start_here = parent.at('div.block#X2')

# this should be a Nokogiri::XML::Element of the nearest, previous h1.
# in this example it's the one with the value 'Foo'
parent_element_h1 = 

# this should be a Nokogiri::XML::Element of the nearest, previous h2. 
# in this example it's the one with the value 'Bar'
parent_element_h2 =

Atenção: O start_hereelemento pode ser qualquer lugar dentro do documento. Os dados HTML é apenas um exemplo. Dito isto, os cabeçalhos <h1>e <h2>poderia ser um irmão de start_hereou um filho de um irmão de start_here.

O método recursivo a seguir é um bom ponto de partida, mas não funciona em <h1>porque é um filho de um irmão de start_here:

def search_element(_block,_style)
  unless _block.nil?
    if _block.name == _style
      return _block
    else
      search_element(_block.previous,_style)
    end
  else
    return false
  end
end

parent_element_h1 = search_element(start_here,'h1')
parent_element_h2 = search_element(start_here,'h2')

Depois de aceitar uma resposta, eu vim com a minha própria solução . Ele funciona como um encanto e eu acho que é muito legal.

Publicado 18/03/2009 em 10:07
fonte usuário
Em outras línguas...                            


6 respostas

votos
10

A abordagem que eu tomaria (se eu estou entendendo o seu problema) é usar XPath ou CSS para procurar o seu elemento "start_here" e o elemento pai que você deseja pesquisar em. Então, recursivamente percorrer a árvore a partir de pai, parando quando bateu o elemento "start_here", e segurando o último elemento que corresponde ao seu estilo ao longo do caminho.

Algo como:

parent = value.search("//body").first
div = value.search("//div[@id = 'X2']").first

find = FindPriorTo.new(div)

assert_equal('Foo', find.find_from(parent, 'h1').text)
assert_equal('Bar', find.find_from(parent, 'h2').text) 

Onde FindPriorToé uma classe simples para lidar com a recursão:

class FindPriorTo
  def initialize(stop_element)
    @stop_element = stop_element
  end

  def find_from(parent, style)
    @should_stop = nil
    @last_style  = nil

    recursive_search(parent, style)
  end

  def recursive_search(parent, style)
    parent.children.each do |ch|
      recursive_search(ch, style)
      return @last_style if @should_stop

      @should_stop = (ch == @stop_element)
      @last_style = ch if ch.name == style
    end

    @last_style    
  end

end

Se esta abordagem não é escalável o suficiente, então você pode ser capaz de otimizar as coisas por reescrever a recursive_searchrecursão para não usar, e também passar em ambos os estilos que você está procurando e acompanhar o último encontrado, então você não tem para atravessar a árvore um tempo extra.

Eu também diria tentativa macaco patching Nó de gancho quando o documento está sendo analisado, mas parece que tudo isso está escrito em C. Talvez você possa ser melhor servido usando algo diferente de Nokogiri que tem um analisador nativa Rubi SAX ( talvez REXML ), ou se a velocidade é a sua verdadeira preocupação, fazer a parte de pesquisa em C / C ++ usando Xerces ou similar. Eu não sei o quão bem estes irão lidar com a análise HTML embora.

Respondeu 18/03/2009 em 16:08
fonte usuário

votos
-1

Se você não sabe a relação entre elementos, você pode procurá-los desta forma (em qualquer lugar do documento):


# html code
text = "insert your html here"
# get doc object
doc = Nokogiri::HTML(text)
# get elements with the specified tag
elements = doc.search("//your_tag")

Se, no entanto, você precisa enviar um formulário, você deve usar mecanizar:


# create mech object
mech = WWW::Mechanize.new
# load site
mech.get("address")
# select a form, in this case, I select the first form. You can select the one you need 
# from the array
form = mech.page.forms.first
# you fill the fields like this: form.name_of_the_field
form.element_name  = value
form.other_element = other_value
Respondeu 18/03/2009 em 20:40
fonte usuário

votos
-1

Você pode procurar os descendentes de um Nokogiri HTML::Elementusando seletores CSS. Você pode atravessar antepassados com o .parentmétodo.

parent_element_h1 = value.css("h1").first.parent
parent_element_h2 = value.css("h2").first.parent
Respondeu 25/03/2009 em 20:15
fonte usuário

votos
2

Talvez isso irá fazê-lo. Eu não tenho certeza sobre o desempenho e se pode haver alguns casos que eu não tenha pensado.

def find(root, start, tag)
    ps, res = start, nil
    until res or (ps == root)
        ps  = ps.previous || ps.parent
        res = ps.css(tag).last
        res ||= ps.name == tag ? ps : nil
    end
    res || "Not found!"
end

parent_element_h1 =  find(parent, start_here, 'h1')
Respondeu 01/04/2009 em 10:11
fonte usuário

votos
0

Esta é a minha própria solução (parabéns ao meu colega de trabalho por me ajudar em um presente!) Usando um método recursivo para analisar todos os elementos, independentemente de ser um irmão ou um filho de outro irmão.

require 'rubygems'
require 'nokogiri'

value = Nokogiri::HTML.parse(<<-HTML_END)
  "<html>
    <body>
      <p id='para-1'>A</p>
      <div class='block' id='X1'>
        <h1>Foo</h1>
        <p id='para-2'>B</p>
      </div>
      <p id='para-3'>C</p>
      <h2>Bar</h2>
      <p id='para-4'>D</p>
      <p id='para-5'>E</p>
      <div class='block' id='X2'>
        <p id='para-6'>F</p>
      </div>
    </body>
  </html>"
HTML_END

parent = value.css('body').first

# start_here is given: A Nokogiri::XML::Element of the <div> with the id 'X2
@start_here = parent.at('div.block#X2')

# Search for parent elements of kind "_style" starting from _start_element
def search_for_parent_element(_start_element, _style)
  unless _start_element.nil?
    # have we already found what we're looking for?
    if _start_element.name == _style
      return _start_element
    end
    # _start_element is a div.block and not the _start_element itself
    if _start_element[:class] == "block" && _start_element[:id] != @start_here[:id]
      # begin recursion with last child inside div.block
      from_child = search_for_parent_element(_start_element.children.last, _style)
      if(from_child)
        return from_child
      end
    end
    # begin recursion with previous element
    from_child = search_for_parent_element(_start_element.previous, _style) 
    return from_child ? from_child : false
  else
    return false
  end
end

# this should be a Nokogiri::XML::Element of the nearest, previous h1.
# in this example it's the one with the value 'Foo'
puts parent_element_h1 = search_for_parent_element(@start_here,"h1")

# this should be a Nokogiri::XML::Element of the nearest, previous h2. 
# in this example it's the one with the value 'Bar'
puts parent_element_h2 = search_for_parent_element(@start_here,"h2")

Você pode copiar / colar uma corrida como ele é como um script ruby.

Respondeu 22/04/2009 em 17:46
fonte usuário

votos
3

Me deparei com este alguns anos tarde demais eu suponho, mas se sentiu compelido a postar porque todas as outras soluções são demasiado complicado.

É uma única instrução com XPath:

start = doc.at('div.block#X2')

start.at_xpath('(preceding-sibling::h1 | preceding-sibling::*//h1)[last()]')
#=> <h2>Foo</h2>    

start.at_xpath('(preceding-sibling::h2 | preceding-sibling::*//h2)[last()]')
#=> <h2>Bar</h2>

Isso acomoda tanto irmãos ou filhos de irmãos anteriores anteriores diretos. Independentemente de qual combina, o last()predicado garante que você obtenha o mais próximo jogo anterior.

Respondeu 11/03/2014 em 23:28
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more