2009-03-18 10 views
7

Sto cercando di riempire le variabili parent_element_h1 e parent_element_h2. Qualcuno può aiutarmi a usare Nokogiri per ottenere le informazioni di cui ho bisogno in queste variabili?Come navigare nel DOM usando Nokogiri

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 = 

Si prega di notare: L'elemento start_here potrebbe essere ovunque all'interno del documento. I dati HTML sono solo un esempio. Detto questo, le intestazioni <h1> e <h2> potrebbero essere un fratello di start_here o un figlio di un fratello di start_here.

Il seguente metodo ricorsivo è un buon punto di partenza, ma non funziona su <h1> perché è un figlio di un fratello di 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') 

Dopo aver accettato una risposta, mi è venuta con my own solution. Funziona come un fascino e penso che sia abbastanza bello.

risposta

3

Mi sono imbattuto in questo alcuni anni troppo tardi, suppongo, ma mi sono sentito obbligato a postare perché tutte le altre soluzioni sono troppo complicate.

Si tratta di una singola istruzione con 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> 

Questo ospita sia i fratelli precedenti diretti o figli di fratelli precedenti. Indipendentemente da quale corrisponde, il predicato last() garantisce di ottenere la corrispondenza precedente più vicina.

10

L'approccio che vorrei prendere (se sto capendo il tuo problema) è usare XPath o CSS per cercare il tuo elemento "start_here" e l'elemento genitore che vuoi cercare sotto. Quindi, cammina ricorsivamente l'albero iniziando dal genitore, fermandosi quando si preme l'elemento "start_here" e tenendo premuto sull'ultimo elemento che corrisponde allo stile lungo il percorso.

Qualcosa di simile:

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) 

Dove FindPriorTo è una semplice classe per gestire la ricorsione:

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 questo approccio non è abbastanza scalabile, allora si potrebbe essere in grado di ottimizzare le cose da riscrivendo il recursive_search per non utilizzare la ricorsione, e anche passare in entrambi gli stili che si stanno cercando e tenere traccia dell'ultima trovata, in modo da non dover attraversare l'albero un tempo extra.

Direi anche di provare il nodo patch di scimmia su cui agganciare quando il documento viene analizzato, ma sembra che tutto questo sia scritto in C. Forse potresti essere servito meglio usando qualcosa di diverso da Nokogiri che ha un nativo Parser SAX Ruby (forse REXML), o se la velocità è la tua vera preoccupazione, fai la parte di ricerca in C/C++ usando Xerces o simili. Non so quanto bene si occuperanno di analizzare l'HTML.

+0

Il problema è che non so se l'intestazione è un fratello o un figlio di un fratello. La tua soluzione presuppone che io sappia se si tratta di un fratello o un figlio di un fratello. Inoltre, i miei dati di esempio sono molto più brevi dei miei dati reali: "my_tag" può essere ovunque all'interno del documento. – Javier

+0

puoi usare '//' invece di '/ html/body /' o anche '/ html/body // div' in XPath quando non sei sicuro della relazione tra fratello e figlio. http://www.w3schools.com/Xpath/ –

+0

Penso che la mia domanda non fosse abbastanza specifica, ho modificato la domanda e spero che sia ora chiaro quello che sto cercando (controlla i commenti sopra le variabili che sono cercando di riempire di dati). – Javier

-1

Se non si conosce il rapporto tra gli elementi, è possibile cercare per loro in questo modo (in qualsiasi parte del 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, tuttavia, è necessario presentare un modulo, è necessario utilizzare Mechanize:


# 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 
+0

Questo non risolve il mio problema, ma ho modificato la mia domanda per essere più specifica. Si prega di notare il commento sopra le due variabili che sto cercando di compilare. – Javier

+0

In breve: ciò non funzionerebbe perché corrisponderebbe più del precedente h1 o h2. – Javier

-1

È possibile cercare i discendenti di un Nokogiri HTML::Element utilizzando selettori CSS. È possibile attraversare gli antenati con il metodo .parent.

parent_element_h1 = value.css("h1").first.parent 
parent_element_h2 = value.css("h2").first.parent 
+0

Questo non restituisce il risultato che sto cercando. Per favore leggi di nuovo la domanda. – Javier

2

Forse questo lo farà. Non sono sicuro delle prestazioni e potrebbero esserci alcuni casi a cui non ho pensato.

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') 
0

Questa è la mia soluzione (complimenti al mio collega per avermi aiutato su questo!) Utilizzando un metodo ricorsivo per analizzare tutti gli elementi a prescindere di essere un fratello o un figlio di un altro fratello.

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") 

È possibile copiarlo/incollarlo eseguendolo come se fosse uno script rubino.