Nokogiri:选择元素A和B之间的内容

让Nokogiri选择start和stop元素之间的所有内容(包括start / stop-element)最聪明的方法是什么?

检查下面的示例代码,了解我在寻找什么:

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

A

Foo

B

C

Bar

D

E

F

F

G

" HTML_END parent = value.css('body').first # START element @start_element = parent.at('p#para-3') # STOP element @end_element = parent.at('p#para-7')

结果(返回值)应如下所示

 

C

Bar

D

E

F

F

更新:这是我目前的解决方案,但我认为必须有更聪明的东西:

 @my_content = "" @selected_node = true def collect_content(_start) if _start == @end_element @my_content << _start.to_html @selected_node = false end if @selected_node == true @my_content << _start.to_html collect_content(_start.next) end end collect_content(@start_element) puts @my_content 

一种使用递归的太聪明的oneliner:

 def collect_between(first, last) first == last ? [first] : [first, *collect_between(first.next, last)] end 

迭代解决方案:

 def collect_between(first, last) result = [first] until first == last first = first.next result << first end result end 

编辑:(简短)解释星号

它被称为splat运算符。 它“展开”一个数组:

 array = [3, 2, 1] [4, array] # => [4, [3, 2, 1]] [4, *array] # => [4, 3, 2, 1] some_method(array) # => some_method([3, 2, 1]) some_method(*array) # => some_method(3, 2, 1) def other_method(*array); array; end other_method(1, 2, 3) # => [1, 2, 3] 
 # monkeypatches for Nokogiri::NodeSet # note: versions of these functions will be in Nokogiri 1.3 class Nokogiri::XML::NodeSet unless method_defined?(:index) def index(node) each_with_index { |member, j| return j if member == node } end end unless method_defined?(:slice) def slice(start, length) new_set = Nokogiri::XML::NodeSet.new(self.document) length.times { |offset| new_set << self[start + offset] } new_set end end end # # solution #1: picking elements out of node children # NOTE that this will also include whitespacy text nodes between the 

elements. # possible_matches = parent.children start_index = possible_matches.index(@start_element) stop_index = possible_matches.index(@end_element) answer_1 = possible_matches.slice(start_index, stop_index - start_index + 1) # # solution #2: picking elements out of a NodeSet # this will only include elements, not text nodes. # possible_matches = value.xpath("//body/*") start_index = possible_matches.index(@start_element) stop_index = possible_matches.index(@end_element) answer_2 = possible_matches.slice(start_index, stop_index - start_index + 1)

为了完整性, XPath唯一的解决方案:)
它构建了两个集合的交集,即start元素的以下兄弟元素和end元素的前一个兄弟元素。

基本上你可以建立一个交叉点:

  $a[count(.|$b) = count($b)] 

对可读性的变量略有不同:

 @start_element = "//p[@id='para-3']" @end_element = "//p[@id='para-7']" @set_a = "#@start_element/following-sibling::*" @set_b = "#@end_element/preceding-sibling::*" @my_content = value.xpath("#@set_a[ count(.|#@set_b) = count(#@set_b) ] | #@start_element | #@end_element") 

兄弟姐妹不包含元素本身,因此开始和结束元素必须分别包含在表达式中。

编辑:更简单的解决方案:

 @start_element = "p[@id='para-3']" @end_element = "p[@id='para-7']" @my_content = value.xpath("//*[preceding-sibling::#@start_element and following-sibling::#@end_element] | //#@start_element | //#@end_element")