2009-03-18 20 views
7

Tôi đang cố gắng điền các biến parent_element_h1parent_element_h2. Bất cứ ai có thể giúp tôi sử dụng Nokogiri để nhận thông tin tôi cần vào các biến đó không?Cách điều hướng DOM bằng 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 = 

Xin lưu ý: Yếu tố start_here có thể là bất cứ nơi nào trong tài liệu. Dữ liệu HTML chỉ là một ví dụ. Điều đó nói rằng, các tiêu đề <h1><h2> có thể là anh chị em của start_here hoặc con của anh chị em của start_here.

Phương pháp đệ quy sau đây là một điểm khởi đầu tốt, nhưng nó không hoạt động trên <h1> bởi vì nó là một đứa con của anh chị em ruột của 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') 

Sau khi chấp nhận một câu trả lời, tôi đã đưa ra với my own solution. Nó hoạt động như một nét duyên dáng và tôi nghĩ nó khá tuyệt.

Trả lời

3

Tôi đã xem qua điều này một vài năm quá muộn Tôi cho rằng, nhưng cảm thấy bị buộc phải đăng vì tất cả các giải pháp khác là quá phức tạp.

Đó là một tuyên bố duy nhất với 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> 

này chứa một trong hai anh em ruột trước trực tiếp hoặc con cái của anh chị em trước. Bất kể cái nào khớp với nhau, vị từ last() đảm bảo rằng bạn nhận được kết quả phù hợp gần nhất trước đó.

10

Cách tiếp cận tôi sẽ thực hiện (nếu tôi hiểu vấn đề của bạn) là sử dụng XPath hoặc CSS để tìm kiếm phần tử "start_here" và phần tử cha bạn muốn tìm kiếm. Sau đó, đệ quy đi bộ cây bắt đầu từ cha mẹ, dừng lại khi bạn nhấn vào phần tử "start_here" và giữ phần tử cuối cùng phù hợp với phong cách của bạn trên đường đi.

Cái gì như:

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) 

đâu FindPriorTo là một lớp đơn giản để xử lý các đệ quy:

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 

Nếu phương pháp này là không đủ khả năng mở rộng, sau đó bạn có thể có thể để tối ưu hóa mọi thứ bằng viết lại các recursive_search để không sử dụng đệ quy, và cũng vượt qua trong cả hai phong cách bạn đang tìm kiếm và theo dõi tìm thấy cuối cùng, vì vậy bạn không cần phải đi qua cây thêm một thời gian.

Tôi cũng sẽ nói thử khỉ Node để móc khi tài liệu được phân tích cú pháp, nhưng có vẻ như tất cả được viết bằng C. Có lẽ bạn có thể được phục vụ tốt hơn bằng cách sử dụng một cái gì đó khác Nokogiri có nguồn gốc Trình phân tích cú pháp SAX của Ruby (có thể là REXML), hoặc nếu tốc độ là mối quan tâm thực sự của bạn, hãy làm phần tìm kiếm trong C/C++ bằng Xerces hoặc tương tự. Tôi không biết làm thế nào tốt sẽ đối phó với phân tích cú pháp HTML mặc dù.

+0

Vấn đề là, tôi không biết liệu đầu trang là anh chị em hay con của anh chị em ruột. Giải pháp của bạn giả định rằng tôi biết nếu đó là anh chị em hoặc con của anh chị em ruột. Bên cạnh đó, dữ liệu ví dụ của tôi ngắn hơn nhiều so với dữ liệu thực của tôi: 'my_tag' có thể ở bất kỳ đâu bên trong tài liệu. – Javier

+0

bạn có thể sử dụng '//' thay vì '/ html/body /' hoặc thậm chí '/ html/body // div' trong XPath khi bạn không chắc chắn về mối quan hệ anh/em. http://www.w3schools.com/Xpath/ –

+0

Tôi nghĩ câu hỏi của mình không đủ cụ thể, tôi đã chỉnh sửa câu hỏi và hy vọng câu hỏi này hiện rõ ràng những gì tôi đang tìm kiếm (kiểm tra các nhận xét ở trên các biến tôi đang cố gắng điền dữ liệu). – Javier

-1

Nếu bạn không biết mối quan hệ giữa các yếu tố, bạn có thể tìm kiếm chúng theo cách này (bất cứ nơi nào trong tài liệu):


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

Tuy nhiên, nếu bạn cần phải gửi biểu mẫu, bạn nên sử dụng cơ giới hóa:


# 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

Điều này không giải quyết được vấn đề của tôi, nhưng tôi đã chỉnh sửa câu hỏi của mình cụ thể hơn. Xin lưu ý nhận xét ở trên hai biến tôi đang cố điền. – Javier

+0

Tóm lại: Điều này sẽ không hoạt động vì nó sẽ khớp với nhiều hơn so với h1 hoặc h2 gần nhất, trước đó. – Javier

-1

Bạn có thể tìm kiếm con cháu của Nokogiri HTML::Element bằng bộ chọn CSS. Bạn có thể đi qua tổ tiên với phương thức .parent.

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

Điều này không trả lại kết quả mà tôi đang tìm kiếm. Vui lòng đọc lại câu hỏi. – Javier

2

Có thể điều này sẽ thực hiện. Tôi không chắc chắn về hiệu suất và nếu có thể có một số trường hợp mà tôi đã không nghĩ đến.

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

Đây là giải pháp của riêng tôi (thanh danh cho đồng nghiệp của tôi đã giúp tôi về thế này!) Sử dụng một phương pháp đệ quy để phân tích tất cả các yếu tố bất kể là anh chị em ruột hoặc con của anh chị em khác.

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

Bạn có thể sao chép/dán để chạy nó giống như tập lệnh ruby.

Các vấn đề liên quan