如何使用Nokogiri导航DOM

我正在尝试填充变量parent_element_h1parent_element_h2 。 谁能帮助我使用Nokogiri将我需要的信息输入这些变量?

 require 'rubygems' require 'nokogiri' value = Nokogiri::HTML.parse(<<-HTML_END) "  

A

Foo

B

C

Bar

D

E

F

" HTML_END parent = value.css('body').first # start_here is given: A Nokogiri::XML::Element of the
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 =

请注意: start_here元素可以位于文档内的任何位置。 HTML数据只是一个例子。 也就是说,标题

可以是start_here的兄弟,也可以是start_here兄弟的子start_here

以下递归方法是一个很好的起点,但它不适用于

因为它是start_here兄弟的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') 

在接受答案后,我提出了自己的解决方案 。 它就像一个魅力,我觉得它非常酷。

我觉得这几年来得太晚了,但觉得有必要发帖,因为所有其他解决方案都太复杂了。

这是XPath的一个声明:

 start = doc.at('div.block#X2') start.at_xpath('(preceding-sibling::h1 | preceding-sibling::*//h1)[last()]') #=> 

Foo

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

Bar

这可以容纳直接的兄弟姐妹或以前的兄弟姐妹的孩子。 无论哪一个匹配, last()谓词都可以确保您获得最接近的先前匹配。

我将采用的方法(如果我理解你的问题)是使用XPath或CSS来搜索你的“start_here”元素和你想要搜索的父元素。 然后,从父级开始递归地遍历树,当你点击“start_here”元素时停止,并保持与你的风格匹配的最后一个元素。

就像是:

 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) 

FindPriorTo是一个处理递归的简单类:

 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 

如果这种方法不够灵活,那么你可以通过重写recursive_search以不使用递归来优化事物,并且还传递你正在寻找的两种样式并跟踪最后找到的样式,所以你不需要不得不额外穿越树。

我还会说在尝试解析文档时尝试使用Monkey修补Node来挂钩,但看起来所有这些都是用C语言编写的。也许你可能会更好地使用Nokogiri之外的东西,它有一个原生的Ruby SAX解析器(也许是REXML ),或者如果速度是您真正关心的问题,请使用Xerces或类似工具在C / C ++中进行搜索部分。 我不知道这些将如何处理解析HTML。

也许这会做到这一点。 我不确定性能,是否有一些我没有想过的情况。

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

这是我自己的解决方案(感谢我的同事帮助我解决这个问题!)使用递归方法解析所有元素,无论是兄弟姐妹还是其他兄弟姐妹的孩子。

 require 'rubygems' require 'nokogiri' value = Nokogiri::HTML.parse(<<-HTML_END) "  

A

Foo

B

C

Bar

D

E

F

" HTML_END parent = value.css('body').first # start_here is given: A Nokogiri::XML::Element of the
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")

您可以像运行ruby脚本一样复制/粘贴它。

如果您不知道元素之间的关系,可以通过这种方式搜索它们(文档中的任何位置):

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

但是,如果您需要提交表格,则应使用机械化:

 # 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 

您可以使用CSS选择器搜索Nokogiri HTML::Element的后代。 您可以使用.parent方法遍历祖先。

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