7

我正在尝试填充变量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 =

请注意:该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')

接受答案后,我想出了自己的解决方案。它就像一个魅力,我认为它很酷。

4

6 回答 6

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) 

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不使用递归来优化事物,并传入您正在寻找的两种样式并跟踪最后找到的样式,因此您没有遍历树一个额外的时间。

我还要说尝试猴子修补节点以在文档被解析时挂钩,但看起来所有这些都是用 C 编写的。也许使用具有本机 Ruby SAX 解析器的 Nokogiri 以外的其他东西可能会更好(可能是REXML),或者如果您真正关心的是速度,请使用 Xerces 或类似工具在 C/C++ 中执行搜索部分。我不知道这些将如何处理解析 HTML。

于 2009-03-18T15:08:07.277 回答
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>

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

于 2014-03-11T22:28:33.183 回答
2

也许这会做到。我不确定性能以及是否可能有一些我没有想到的情况。

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')
于 2009-04-01T09:11:27.560 回答
0

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

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

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

于 2009-04-22T16:46:24.853 回答
-1

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


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

但是,如果您需要提交表单,则应使用 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
于 2009-03-18T19:40:12.787 回答
-1

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

parent_element_h1 = value.css("h1").first.parent
parent_element_h2 = value.css("h2").first.parent
于 2009-03-25T19:15:37.240 回答