2009-03-18 3 views
7

변수 parent_element_h1parent_element_h2을 채우려고합니다. 누구든지 그 변수에 필요한 정보를 얻기 위해 Nokogiri을 사용할 수 있습니까?Nokogiri를 사용하여 DOM을 탐색하는 방법

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의 형제가 될 수 있습니다.

다음 재귀 방법은 좋은 출발점이지만 start_here의 형제의 자식이기 때문에이 <h1>에서 작동하지 않습니다 :

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

이 답변에 동의 한 후, 내가 와서 my own solution. 그것은 매력처럼 작동하고 나는 그것이 꽤 멋지다라고 생각한다.

답변

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() 술어는 가장 가까운 이전 일치를 확보합니다.

10

내가 취한 접근법 (문제를 이해하는 경우)은 "start_here"요소 및 검색 할 상위 요소를 검색하기 위해 XPath 또는 CSS를 사용하는 것입니다. 그런 다음 부모에서 시작하여 재귀 적으로 나무를 걷고, "start_here"요소를 치면 멈추고 길을 따라 스타일에 맞는 마지막 요소를 붙잡습니다.

뭔가 같은 :

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 

이 방법은 충분한 확장 성이없는 경우, 다음으로 일을 최적화 할 수 있습니다 FindPriorTo 간단한 클래스는 재귀를 처리하는 것입니다

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) 

recursive_search을 재귀를 사용하지 않도록 다시 작성하고, 찾고있는 스타일을 모두 전달하고 마지막으로 찾은 것을 추적하므로 트리를 추가로 트래버스하지 않아도됩니다.

나는 또한 원숭이 패치를 시도 할 것이라고 말하고 싶다. 문서가 파싱 될 때 노드가 연결되지만, 그 모두가 C로 작성된 것처럼 보일 것이다. 아마도 네이티브가있는 Nokogiri 이외의 것을 사용하여 더 잘 서비스 할 수있을 것이다. Ruby SAX 파서 (어쩌면 REXML) 또는 속도가 정말 걱정된다면 C/C++에서 Xerces 등을 사용하여 검색 부분을 수행하십시오. 나는 HTML을 파싱하는 것이 얼마나 잘 처리 될지 모르겠다.

+0

문제는 헤더가 형제 또는 형제 자매인지 여부입니다. 당신의 솔루션은 내가 형제인지 형제인지를 알고 있다고 가정합니다. 그 외에도 예제 데이터는 실제 데이터보다 훨씬 짧습니다. 'my_tag'는 문서 내부의 어느 위치 에나있을 수 있습니다. – Javier

+0

형제/자식 관계가 확실하지 않은 경우 XPath에서 '/ html/body /'또는 '/ html/body // div'대신 '//'을 사용할 수 있습니다. http://www.w3schools.com/Xpath/ –

+0

제 생각에는 제 질문이 구체적이지 않다고 생각합니다. 질문을 편집했고 지금 내가 원하는 것을 분명히하기를 바랍니다. (변수 위에있는 주석을 확인하십시오. 데이터로 채우기 위해 노력하고 있음). – Javier

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

것은, 당신이 사용해야을 기계화 :


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

이것은 내 문제를 해결하지 못하지만 좀 더 구체적으로 내 질문을 편집했습니다. 채우려는 두 변수 위에있는 주석을 주목하십시오. – Javier

+0

간단히 말해서, 가장 가까운 h1 또는 h2보다 더 일치하기 때문에 작동하지 않습니다. – Javier

-1

당신은 CSS 선택기를 사용하여 노코 기리 HTML::Element의 후손을 검색 할 수 있습니다. .parent 메소드를 사용하여 조상을 트래버스 할 수 있습니다.

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

이것은 찾고있는 결과를 반환하지 않습니다. 질문을 다시 읽으십시오. – Javier

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

루비 스크립트처럼 실행하여 복사/붙여 넣기를 할 수 있습니다.