2009-05-04 5 views
4

Nokogiri가 시작 요소와 중지 요소 (시작/중지 요소 포함) 사이의 모든 콘텐츠를 선택하는 가장 효율적인 방법은 무엇입니까?Nokogiri : 요소 A와 B 사이의 콘텐츠 선택

<p id='para-3'>C</p> 
<p class="that">Bar</p> 
<p id='para-4'>D</p> 
<p id='para-5'>E</p> 
<div class='block' id='X2'> 
    <p id='para-6'>F</p> 
</div> 
<p id='para-7'>F</p> 

업데이트 :이과 같아야합니다

require 'rubygems' 
require 'nokogiri' 

value = Nokogiri::HTML.parse(<<-HTML_END) 
    "<html> 
    <body> 
     <p id='para-1'>A</p> 
     <div class='block' id='X1'> 
     <p class="this">Foo</p> 
     <p id='para-2'>B</p> 
     </div> 
     <p id='para-3'>C</p> 
     <p class="that">Bar</p> 
     <p id='para-4'>D</p> 
     <p id='para-5'>E</p> 
     <div class='block' id='X2'> 
     <p id='para-6'>F</p> 
     </div> 
     <p id='para-7'>F</p> 
     <p id='para-8'>G</p> 
    </body> 
    </html>" 
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') 

결과 (반환 값) : 아래

확인 예제 코드는 내가 무엇을 찾고 이해하는이 내입니다 현재의 솔루션이지만, 나는 더 똑똑한 것이 있어야한다고 생각한다.

@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 

답변

10

재귀 사용하는 방법은 너무나 스마트 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 

편집 : 별표의 (짧은) 설명이

그것은라고 스 플랫 연산자. 그것은 배열 "언 롤링"

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] 
+0

감사합니다! 하지만 collect_between()가 재귀 호출하기 전에 '*'가 무엇을 의미하는지 이해하지 못합니다. 당신은 정교 할 수 있습니까? – Javier

+1

원본 답안에 약간의 설명을 추가했습니다. 구글은 "splat operator"를 더 많이 :-) –

+0

고마워! 호기심에서 벗어나 : "스 플랫 (splat) 연산자"가 어디에 문서화되어 있는지 알고 있습니까? http://www.ruby-doc.org/core/ – Javier

2
# 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 <p> 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) 
+0

... 나는 정말로 노코 기리를 고대하고있다. 1.3.:) – Javier

+0

NodeSet # slice 및 NodeSet # index가 github의 Nokogiri 마스터에 있음을 유의하십시오. 이것들은 이번 달 말에 1.3.0 릴리스에 포함될 것입니다. –

2

완성도를 위해서를 의 XPath 만 솔루션 : 그것은 두 세트의 교차로, 시작 요소의 다음 형제 및 이전을 구축
최종 요소의 형제 자매. 기본적으로

당신이 교차로를 구축 할 수 있습니다 :

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