IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  XML | 오픈 소스 | 자바  >

루비 온 레일스와 XML (한글)

XML 문서를 다루기 위한 레일스 스텁(stub) 생성하기

developerWorks
Go to the previous page12 페이지 중 7 페이지Go to the next page

문서 옵션

샘플 코드


제안 및 의견
피드백

튜토리얼 평가

이 컨텐츠를 개선하기 위한 도움을 주십시오.


XML 파싱하고 다루기

지금 상사는 자기가 좋아하는 Fish Head Curry 요리의 점수가 낮다고 열 받아 있고, 여러분 XML을 파싱하고 처리하는 기능을 구현해야 하는 부담 때문에 걱정이다. 하지만 이 튜토리얼을 따라 해보면서 XML API들이 쓰기 쉽다는 걸 알았기 때문에, 당장에라도 작업을 시작하고 상사가 불만을 터트린 요리들에 대해 높은 평가를 부여해줌으로써 상사를 기쁘게 할 수 있다.

이 섹션의 목표는 업로드한 XML 문서를 파싱하여 검색하고 반복하면서 Fish Head Curry와 Pig Organ Soup 요리를 찾아낸 후, 이 요리들의 평가점수를 6으로 올리는 것이다. 그러면 상사의 기분이 좀 더 나아질 거라 예상되며, 운이 좋다면 그가 다른 요리의 점수에 신경쓰기 전에 여러분이 작성한 코드가 꽤 훌륭하다는 것을 눈치챌 수도 있다.

REXML로 파싱하기

이 섹션에서는 REXML로 예제 문서를 파싱하고 관련된 엘리먼트들을 적절하게 업데이트한다. REXML로 XML 문서를 돌아다니는 것은 배열이나 XPath 쿼리, 또는 단순한 반복자를 사용하여 엘리먼트와 속성 등에 접근하는 것과 마찬가지로 엄청나게 쉽다. 참고자료에 REXML과 관련된 다른 훌륭한 자료를 나열해놨다. 허나 지금은 일단 일을 시작해보자. 먼저 모든 DishName을 찾는 XPath 쿼리를 사용해 보자(Listing 26 참고)


Listing 26. REXML을 사용하여 DishName 엘리먼트를 찾기 위핸 XPath 클래스
                    
  def parse_with_rexml( xml_data )
    doc = REXML::Document.new( xml_data )

    REXML::XPath.each( doc, "//DishName" ){ |dish_name_element|
      #... your code here ...
      #... do work with elements ...
    }

    doc.write( out_string = "", 2 )
    hijack_response( out_string )
  end

이 몇 줄 안 되는 작은 코드는 문서에서 DishName 엘리먼트를 전부 찾아내고 반복하면서, 이 각각의 엘리먼트들에 대하여 필요한 일을 하기 위해 블록 안에 커스텀 코드를 넣는 것을 보여준다. //DishName이라는 문자열은 XPath 쿼리인데, 기본적으로 "이 문서에서 모든 DishName 엘리먼트를 찾아내라"는 뜻이다.

이제 엘리먼트의 텍스트 값이 "Fish Head Curry"와 "Pig Organ Soup"인지 체크해 본다(Listing 27).


Listing 27. 주어진 XML 엘리먼트에서 텍스트 값 가져오기
                    
if dish_name_element.text == "Fish Head Curry" or 
   dish_name_element.text == "Pig Organ Soup"
  #... your code here ...
  #... do work with elements ...
end

REXML 엘리먼트의 text 메서드를 호출하면 (만약 있을 경우) 첫 번째 하위 텍스트 엘리먼트나 없으면 nil을 반환한다.

찾고자 하는 DishName 엘리먼트를 얻고 나면, 상위 엘리먼트를 알아내야 한다. 문서의 구조를 상기해 보자(Listing 28).


Listing 28. XML 샘플 일부분
                    
<Food>
  ...
  <Dish rating="2" category="singaporean">
    <DishName>Fish Head Curry</DishName>
    <WhereToBuy>Little India</WhereToBuy>
  </Dish>
  ...
</Food>

상위 엘리먼트는 Dish다. 이것에 접근하기 위해 parent 메서드를 호출한다(Listing 29).


Listing 29. REXML을 사용하여 XML 엘리먼트의 parent 엘리먼트 가져오기
                    
parent = dish_name_element.parent

상위 엘리먼트를 얻었으면, 이것의 rating 속성에 접근하여 새 값을 대입한다(Listing 30).


Listing 30. REXML을 사용하여 XML 엘리먼트의 속성에 새 값 대입하기
                    
parent.attributes["rating"] = 6

여기까지 진행했으면à 이제 여러분 상사를 기쁘게 해줄 수 있을 것이다. Listing 31의 전체 코드 블록을 빨리 훑어보자.


Listing 31. REXML을 사용하여 XML 엘리먼트를 돌아다니고, 파싱한 후 수정하는 코드 블록
                    
XPath.each( doc, "//DishName" ){ |dish_name_element|
  if dish_name_element.text == "Fish Head Curry" or 
     dish_name_element.text == "Pig Organ Soup"
    parent = dish_name_element.parent
    parent.attributes["rating"] = 6
  end
}

이제 상사를 감동시키고자 새 평가점수가 반영된 XML 문서를 새로 기록하는 코드를 한 줄로 작성해 보기로 하자(역자 주: 여기서 '한 줄'의 뜻은 문서상에서 한 줄로 표현된다는 것이 아닌, 단일 루비 구문(statement)이라고 보는 것이 맞다). 지금쯤 스스로를 루비 고수로 느끼고 있는 여러분에게는 사실 별 거 아니다. 가독성을 위하여 세 줄로 나누었다(Listing 32).


Listing 32. 코드 한 줄로 XML 샘플을 다른 방식으로 파싱하고 처리하는 방법.
                    
doc.root.each_element( "//DishName" ){ |e|
  e.parent.attributes["rating"] = 6 
    unless ["Fish Head Curry", "Pig Organ Soup"].index( e.text ).nil? }

이 방법은 XPath를 명시적으로 사용하지 않는다. 대신 REXML의 Element 클래스에 있는 each_element 메서드를 사용한다. 이 코드를 문장처럼, 왼쪽에서 오른쪽으로 읽어보면 다음과 같은 문장이 된다(중요한 단어는 볼드체).

"각각의 DishName 엘리먼트를 찾아, 엘리먼트의 텍스트 값이 'Pig Organ Soup'나 'Fish Head Curry'이 아닌 경우는 제외하고, 상위 엘리먼트의 rating 속성에 6대입해라."

자, 다음엔 Hpricot을 가지고 재미있게 놀아보자!




위로


Hpricot으로 파싱하기

Hpricot은 기술적으로는 XML 파서가 아닌 HTML 파서다. 하지만 HTML과 XML이 근본적으로는 같은 문법을 사용하기 때문에 Hpricot으로도 XML 문서를 파싱할 수 있다. 많은 사람들이 속도(스캐너가 C로 만들어졌다)와 루비스트들(Rubyists)이 흔히 말하는 편리하고 매력적인 문법(syntactical sugar)을 이유로 REXML보다는 Hpricot을 선택한다.

Hpricot으로 XML을 다룰 때 데이터에 대소문자 구별이 필요한 경우 몇 가지 이슈에 주의해야 하는데, (마지막) 버전 0.5조차도 XML을 명시적으로 파싱하면서 모든 엘리먼트 이름들을 소문자로 바꿔버린다. Hpricot Trac에 있는 티켓(#53)에서 이를 해결해 달라는 요청이 있지만 아직 해결되지 않고 있다. 따라서 Hpricot을 사용하고 싶다면 이 사항을 숙지하고 진행하기 바란다. 이제 문서를 파싱하고 새 평가를 부여하는 코드를 보자. REXML과 매우 비슷하다(Listing 33).


Listing 33. Hpricot을 사용하여 XML 문서를 파싱하고, 돌아다니며 처리하기
                    
  def parse_with_hpricot( xml_data )
    doc = Hpricot.XML( xml_data )
    (doc/:dishname).each{ |dish_name_element|
      if dish_name_element.inner_html == "Fish Head Curry" or 
         dish_name_element.inner_html == "Pig Organ Soup"
        parent = dish_name_element.parent
        parent.attributes["rating"] = "6"
      end
    }
  end

(main_controller.rb의 맨 위에 'require rubygem'와 'require hpricot' 넣는 것을 잊지 말자!)

Listing 33에서 흥미로운 부분은 Listing 34에 있는 코드 조각이다.


Listing 34. Hpricot의 나누기(divisor) 메서드
                    
(doc/:dishname)

루비에서는 모든 것이 객체다. 원시 타입(primitives)이 없기 때문에 메서드 이름으로 나누기(/)를 사용하는 것이 가능하다. Hpricot의 나누기 메서드는 search 메서드의 별칭(alias)일 뿐이므로 Listing 34에 나온 것과 똑같은 일을 다른 방법으로 할 수 있다. Listing 35에 나와있다.


Listing 35. Hpricot의 search 메서드(별칭으로 /를 사용 가능)
                    
doc.search(:dishname)

루비에서는 메서드를 호출할 때 괄호를 생략할 수 있다는 것을 기억해 두자. 가독성을 고려하여 괄호를 붙일지 말지를 정할 수 있다. 그러한 차이점을 빼면 나머지 XML을 처리하는 코드는 거의 똑같다.

Hpricot의 CSS와 XPath selectors 등을 사용하면 인상적인 코드들을 많이 작성할 수 있다. Hpricot을 더 잘 활용하려면 Hpricot 웹 사이트의 예제들을 살펴보는 것을 추천한다.




위로


REXML을 사용하여 XML을 재귀적으로 파싱하기

때때로 XML 문서를 파싱은 하는데, 구조나 내용 또는 어떤 것을 찾아보게 될지 모를 때가 있다. 그런 경우에 여러분은 특정 엘리먼트들을 찾는 것이 필요하지 않거나, (XML을 사용하기 쉽도록) 어떤 메모리에 올라갈 데이터 구조를 빌드하고 싶을 것이다. REXML을 사용하면 이러한 것을 믿기 어려울 정도로 쉽게 할 수 있다(Listing 36).


Listing 36. 전체 XML 문서를 재귀적으로 반복하기
                    
  def parse_recursive( xml_data )
    doc = REXML::Document.new( xml_data )
    root = doc.root
    root.each_recursive{ |element| 
      logger.info "Element: #{element}" 
    }
    redirect_to :action => 'index'
  end

end

(log/development.log 파일에 보면 나와있다. 대부분의 메시지가 위 코드의 출력 결과일 것이다.)

기술적으로 보면, 전체 문서를 재귀적으로 돌면서 각 엘리먼트의 로깅을 남기는 코드는 사실 단 한 줄이다. 실제 작업 내용을 잘 보여주기 위해 세 줄로 나눈 것이다. 이것은 다음과 같이 한 줄짜리 코드로 만들 수 있다(Listing 37).


Listing 37. 전체 XML 문서를 재귀적으로 반복하는 한 줄짜리 코드
                    
                    root.each_recursive{ |element| logger.info "Element: #{element}" }
                

REXML API에는 여러 가지 편리한 메서드가 많이 들어있다. 예를 들어, 전체 문서를 재귀적으로 반복하는 것이 아닌, 특정 엘리먼트 바로 밑에 있는 엘리먼트들만 반복하고 싶을 경우, Listing 38에 나온 대로 하면 된다.


Listing 38. 특정 엘리먼트의 바로 밑에 있는 엘리먼트들 반복하기
                    
doc = REXML::Document.new( xml_data )
root = doc.root
root.each_element{ |child| logger.info "Child Element: #{child}" }

이것으로 XML과 루비 온 레일스에 대해 마치겠다.




위로



Go to the previous page12 페이지 중 7 페이지Go to the next page
    IBM 소개 개인정보 보호정책 문의