如何使用Nokogiri导航DOM

Jav*_*ier 7 ruby xpath dom ruby-on-rails nokogiri

我正在尝试填补变量parent_element_h1parent_element_h2.任何人都可以帮助我使用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 =
Run Code Online (Sandbox Code Playgroud)

请注意:start_here元素可以位于文档中的任何位置.HTML数据只是一个例子.这就是说,头部<h1><h2>可能是兄弟姐妹start_here或兄弟姐妹的孩子start_here.

下面的递归方法是一个很好的起点,但它不起作用,<h1>因为它是一个兄弟的孩子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')
Run Code Online (Sandbox Code Playgroud)

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

Aar*_*nni 10

我将采用的方法(如果我理解你的问题)是使用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) 
Run Code Online (Sandbox Code Playgroud)

哪里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
Run Code Online (Sandbox Code Playgroud)

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

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


Mar*_*mas 3

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

这是带有 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>
Run Code Online (Sandbox Code Playgroud)

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