 |
|
새 XML 문서 만들기
여러분은 API를 사용하는 것부터 손으로 직접 작성하는 것까지 다양한 방법으로 XML 문서를 만들 수 있다. 이 섹션에서는 REXML과 빌더를 사용하여 XML 문서를 만드는 법을 보여준다. 똑같은 문서를 생성하는 데 두 API를 모두 사용해볼 것이기 때문에, 두 API의 문법이 어떻게 다른지 잘 느껴볼 수 있을 것이다.
XML 문서 샘플
XML 문서 생성 및 파싱 예제를 위해 음식 리스트를 나타내는 샘플 문서를 사용할 것이다. Listing 5에 여러분이 만들어 사용할 XML 문서의 기본 구조가 나와있다.
Listing 5. 이 튜토리얼을 진행하는 동안 사용할 XML 샘플
<Food>
<Dish rating="2" category="singaporean">
<DishName>Fish Head Curry</DishName>
<WhereToBuy>Little India</WhereToBuy>
</Dish>
...
<Dish rating="8" category="mexican">
<DishName>Pork Enchilada</DishName>
<WhereToBuy>Iguana Cafe</WhereToBuy>
</Dish>
</Food>
|
실제 생성되는 XML 문서에는 싱가포르에서 내가 좋아하는 몇몇 음식집들의 요리를 나타내는 Dish 라는 엘리먼트들(elements)이 들어가게 된다. 이 목록에는 끔찍할 정도로 맛없는 몇몇 요리도 포함되어 있으므로, 일부 요리들에 대해서는 낮은 평가점수를 부여했다.
우선은 이 문서 데이터를 컨트롤러 소스코드 내에 하드코딩할 것이다. 즉, 데이터는 REXML과 빌더를 사용하여 XML을 생성하기 쉽도록 컨트롤러(main_controller.rb) 내에 Hash의 Array 형태로 저장될 것이다. Listing 6에는 컨트롤러 내에 데이터가 하드코딩되어 저장된 내용 일부가 나와있다.
Listing 6. XML 샘플을 생성하기 위해 사용된 루비 데이터 구조(배열과 해시)
class MainController < ApplicationController
DISHES = [
{ :rating =>2, :category => "singaporean",
:dish_name => "Fish Head Curry", :where_to_buy => "Little India" },
{ :rating =>8, :category => "western", :dish_name => "Cowboy Burger",
:where_to_buy => "Brewerkz" },
# ... Other hashes here...
]
...
|
다음으로 넘어가서, REXML을 사용하여 이 데이터를 XML로 바꾸는 방법을 살펴보자.
REXML
이미 언급했듯이, REXML은 루비 코어와 함께 배포되는 표준 XML API다. 그러므로 언제나 API가 사용 가능하다고 믿어도 된다. REXML로 코드를 작성하는 것은 꽤 단순 명료한데, 프로그램적으로 큰 XML을 생성할 때는 많은 사람들이 빌더를 더 선호한다. 빌더는 잠시 후에 다룰 것이다. 이 섹션에서 나오는 목록들에 나오는 예제 코드들은 모두 main_controller.rb에서 찾아볼 수 있다. 이제 Listing 7을 통해 REXML을 사용하여 새 XML 문서를 만드는 법을 살펴보자.
Listing 7. REXML을 사용하여 새 XML 문서 만들기
...
{ :rating =>8, :category => "mexican", :dish_name => "Pork Enchilada",
:where_to_buy => "Iguana Cafe" }
]
private
def generate_rexml
doc = REXML::Document.new
end
|
정말 하나도 어렵지 않다. 그렇지 않은가? 그럼 루트 엘리먼트(root element)를 추가하는 것은 어떻게 할까? Listing 8에 나와있다.
Listing 8. REXML document에 루트 노드 만들기
...
def generate_rexml
doc = REXML::Document.new
root = doc.add_element( "Food" )
end
end |
이제 XML 샘플 문서의 기본 구조가 만들어졌으므로, 하드코딩된 데이터 구조를 반복하며 나머지 엘리먼트들을 생성해보자.
Listing 9. REXML을 사용하여 XML 샘플의 내용 생성하기
...
root = doc.add_element( "Food" )
DISHES.each{ |element_data|
dish_element = root.add_element( "Dish" )
dish_element.add_attribute( "rating", element_data[:rating] )
dish_element.add_attribute( "category", element_data[:category] )
dish_name_element = dish_element.add_element( "DishName" )
dish_name_element.add_text( element_data[:dish_name] )
where_to_buy_element = dish_element.add_element( "WhereToBuy" )
where_to_buy_element.add_text( element_data[:where_to_buy] )
}
...
|
여러분이 루비에서 배열을 반복하는 문법을 잘 알고 있다고 믿고 계속 진행하겠다. Listing 9에 보이는 것과 같이, 코드는 Array에 대해 루프를 반복하면서 각 Hash마다 하나의 XML Dish 엘리먼트를 만든다. Dish 엘리먼트에는 두 개의 속성(attribute)인 "rating"과 "category", 그리고 두 개의 하위 엘리먼트(child element)인 "DishName"과 "WhereToBuy"를 할당한다. 코드를 명료하고 읽기 쉽도록 변수 이름들과 문법을 약간 상세하게 드러나도록 작성했다. 이제 성공적으로 XML 문서를 생성했으니, 이를 클라이언트에 보낼 수 있도록 오브젝트로 만들 필요가 있다. REXML::Document 클래스의 멋진 점 중 하나는 << string 형식의 메서드를 가지는 임의의 객체에 write 메서드를 사용하여 내용을 기록할 수 있다는 것이다. 한 가지 더 다행인 것은, 루비의 String 클래스가 << string 형식의 메서드를 가지고 있기 때문에 다음과 같이 XML 문서를 쉽게 String으로 만들 수 있다(Listing 10).
Listing 10. REXML 문서의 내용을 String으로 만들기
...
where_to_buy_element.add_text( element_data[:where_to_buy] )
}
doc.write( out_string = "", 2 )
return out_string
end
end
|
주목할 것은, 루비는 Listing 10과 같이 필요하면 코드의 어느 부분에서든 변수를 초기화할 수 있다. doc.write를 호출할 때 매개변수를 넘기는 부분에서 out_string 변수가 초기화되었다(두 번째 매개변수는 들여쓰기 값을 나타낸다). 자바와 같은 기타 일반적인 언어들은 Listing 11과 같이 이를 두 단계로 나눠 해야만 하는 경우가 많다.
Listing 11. REXML 문서의 내용을 String으로 만드는 또 다른 방법
out_string = ""
doc.write( out_string, 2 )
|
루비에서도 이런 식으로 쓸 수는 있지만, 코드에서 불필요한 라인은 없애는 것이 좋다. 이제 실제로 생성된 XML 문서 결과를 확인하기 위해 레일스가 응답하는 부분을 가로채서, 생성된 XML 문서를 브라우저에 보내는 코드를 살펴볼 텐데, 그 전에 먼저 똑같은 XML 문서를 REXML 대신 빌더를 사용하여 만드는 방법부터 살펴보자.
빌더
빌더를 사용하면 XML 마크업 생성코드를 아름답게 작성할 수 있으며 믿을 수 없을 정도로 쉽다. 정말이지, 이 섹션의 나머지 부분을 읽고 나면 아마 여러분조차도 이번 주 내내 빌더가 얼마나 멋진지를 생각하며 푹 빠져있을지도 모른다.
Listing 12에서는 문서를 생성하는 방법을 보여준다.
Listing 12. 빌더를 사용하여 새 XML 문서 만들기.
def generate_builder
doc = Builder::XmlMarkup.new( :target => out_string = "",
:indent => 2 )
end
|
빌더가 생성자 매개변수 중 하나로 대상(target)을 받는다는 것에 주목하자. REXML에서는 API를 통해 XML을 생성한 후 대상이 되는 출력 객체를 선택했었다. 빌더는 단지 출력하고자 하는 생성된 XML 문서를 보여달라고 객체에 요청을 한다. REXML 예제와 마찬가지로 여기서도 똑같이 인라인 변수 선언을 사용한 것을 주목하기 바란다.
이제, 루비의 method_missing 콜백의 진정한 힘을 느껴보고 빌더 API가 이를 어떻게 활용하는지 보자. Food 엘리먼트를 생성하는 방법이 Listing 13에 나와있다.
Listing 13. 빌더를 사용하여 XML 샘플 문서의 루트 엘리먼트 만들기.
def generate_builder
doc = Builder::XmlMarkup.new( :target => out_string = "", :indent => 2 )
doc.Food
end
|
이 시점에서, 아마 여러분은 "잠깐, XML API에는 Food라는 메서드가 없잖아요. 바보 같아!"와 같은 말을 할지도 모른다. 그리고 여러분 말이 맞을 것이다. 여기서 어떻게 되냐하면, 루비는 "으잉? 이 객체에는 Food 메서드가 없네요. 그럼 전 이 호출을 method_missing으로 보내서, API가 처리하고 싶다면 알아서 처리하도록 하겠습니다. API가 안 하겠다면 제가 예외를 발생시겠습니다"라고 하게 된다. 따라서 본질적으로, XML 문서가 가지고 있는 것은 Listing 14에 나온 대로다.
Listing 14. 위와 같이 빌더 document 객체에 메서드 호출을 해서 만들어진 XML
이제, 중첩된 엘리먼트들(nested elements)과 속성들(attributes), 그리고 텍스트 노드들(text nodes)를 만드는 방법을 살펴보자.
Listing 15. 빌더를 사용하여 XML 샘플 문서를 생성하기
def generate_builder
doc = Builder::XmlMarkup.new( :target => out_string = "", :indent => 2 )
doc.Food {
DISHES.each{ |element_data|
doc.Dish( "rating" => element_data[:rating],
"category" => element_data[:category] ){
doc.DishName( element_data[:dish_name] )
doc.WhereToBuy( element_data[:where_to_buy] )
}
}
}
return out_string
end
|
바로 이거다. 이 몇 줄 안 되는 멋진 코드가 빌더를 사용하여 XML 샘플 문서를 만드는 코드의 전부다.
코드 블록을 중첩할 때마다 중첩된 XML 엘리먼트가 만들어지는 것을 볼 수 있다. XML 엘리먼트에 속성을 부여하고 싶을 경우는, 매개변수로 Hash 객체를 넘기면 속성이 만들어진다. 예를 들어, 위 코드에서 Listing 16의 호출 결과는 Listing 17의 XML 엘리먼트와 같을 것이다.
Listing 16. 빌더를 사용하여 속성이 있는 XML 엘리먼트 만들기
doc.Dish( "rating" => element_data[:rating],
"category" => element_data[:category] )
|
Listing 17은 만들어진 XML 엘리먼트다.
Listing 17. Listing 16을 통해 만들어진 XML 결과
<Dish rating="8" category="western">
|
이 튜토리얼에서는 텍스트 데이터와 속성을 지닌 엘리먼트를 만드는 경우에 대해서는 다루지 않는다. 간단한 예제를 통해 기본 방법만 알아두고 넘어가자. Listing 18과 같은 XML 엘리먼트가 있다고 생각해 보자.
Listing 18. 속성과 텍스트가 있는 XML 엘리먼트
<Dish rating="8" category="western">Cowboy Burger</Dish>
|
위 XML 엘리먼트를 생성하려면, 빌더에서는 Listing 19와 같이 먼저 String 타입의 인자를 전달하고 그 다음으로 Hash 타입의 인자를 전달한다.
Listing 19. 빌더를 사용하여 속성과 텍스트가 있는 XML 엘리먼트 만들기
doc.Dish( "Cowboy Burger",
"rating" => 8, "category" => "western" )
|
보다시피 빌더의 문법은 정말 사용하기 쉽고 놀라울 정도로 직관적이다. 지금까지 얘기한 것들과 더불어 표 1에 빌더의 document 객체에서 사용 가능한 메서드들 일부를 정리했다.
표 1. 빌더 document 객체에서 사용 가능한 메서드들
| 메서드 이름 | 액션 |
|---|
cdata!
| XML 마크업에 CDATA 섹션을 넣는다. |
comment!
| 마크업에 XML 코멘트를 넣는다. |
declare!
| 마크업에 XML 선언부를 넣는다. |
instruct!
| 마크업에 프로세싱 인스트럭션을 넣는다. |
target!
| 문서의 대상 객체를 리턴한다(XML이 적힐 오브젝트를 뜻함) |
빌더에 대한 더 자세한 정보는 RubyForge의 RDoc을 참고하기 바란다. 이제 이렇게 만들어진 XML을 어떻게 브라우저에 전달할지 살펴보자.
|