이번 포스팅에서는 LINQ 를 통한 XML 을 파싱하는 과정을 배워보도록 하겠습니다.
우선 LINQ 및 XML 의 사전 지식이 있다고 가정하고 진행하겠습니다.
(적어도 XML의 문서구조, LINQ, 람다식 의 사용 방법 은 숙지 하셔야 합니다.)
.NET Framework 3.5 이상 부터 제공하는 XElement 클래스를 사용하면 XML 구성요소를 보다 쉽게 파싱이 가능합니다.
XElement 클래스는 System.Xml.Linq 네임스페이스 에서 제공하며,
XElement 클래스는 XML의 요소를 나타낼 수 있습니다.
우선, XML 파일을 파싱한다는 가정하에 진행하도록 하겠습니다.
네이버 오픈API 는 XML 형태로 위와 같이 서비스를 제공하고 있습니다.
우리가 파싱해볼 데이터는
channel 노드의 하위 노드의 데이터 (title, link, description... 등)
반복되는 item 노드 및 하위 데이터 (title, link, image, author... 등)
이렇게 크게 두 파트로 나누어 파싱을 진행해보도록 하겠습니다.
public class NaverObject { public string title { get; set; } public string link { get; set; } public string description { get; set; } public string lastBuildDate { get; set; } public int total { get; set; } public int start { get; set; } public int display { get; set; } public List- item { get; set; } public NaverObject() { item = new List
- (); } } public class Item { public string title { get; set; } public string link { get; set; } public string image { get; set; } public string author { get; set; } public double price { get; set; } public int discount { get; set; } public string publisher { get; set; } public int pubdate { get; set; } public string isbn { get; set; } }
XML 요소를 맵핑할 데이터 클래스를 선언 하였습니다.
NaverObject 는 channel 노드의 하위 노드의 데이터 (title, link, description... 등) 를 맵핑할 것이고,
Item 은 반복되는 item 노드 하위 데이터 (title, link, image, author... 등) 를 맵핑 할 것입니다.
그리고 getNaverObject 메서드는 모든 XML 의 요소를 파싱하여 맵핑한 NaverObject 객체를 반환하는 메서드를 만들 것 입니다.
//private const string url = "http://naver.xml"; private string url = "naver.xml"; public NaverObject getNaverObject() { //XElement 요소 Load XElement root = XElement.Load(url); //맵핑할 객체 생성 NaverObject naver = new NaverObject(); //xml 요소 파싱후 객체에 저장 naver.title = root.Element("channel").Element("title").Value; naver.link = root.Element("channel").Element("link").Value; naver.description = root.Element("channel").Element("description").Value; naver.lastBuildDate = root.Element("channel").Element("lastBuildDate").Value; naver.total = root.Element("channel").Element("total").Value.Equals("") ? 0 : Convert.ToInt32(root.Element("channel").Element("total").Value); naver.start = root.Element("channel").Element("start").Value.Equals("") ? 0 : Convert.ToInt32(root.Element("channel").Element("start").Value); naver.display = root.Element("channel").Element("display").Value.Equals("") ? 0 : Convert.ToInt32(root.Element("channel").Element("display").Value); }
XElement 객체를 구성하기전 정적 Load 메서드는 총 8개의 오버로드된 메서드를 제공하고 있으며,
이번 시간에 사용해볼 대상은 파일 및 스트림을 통해 XML 위치를 받고 구성하는 정적 메서드를 사용해 보겠습니다.
XElement 객체에 Load 를 하면 내부적으로 XML 요소를 로드 및 구성 하게 됩니다.
<그림> 스트림 및 String 을 통해 XML 데이터를 받음.
XElement 객체에 XML 을 로드 하고 구성이 되면, <rss> 부터 요소가 시작됩니다.
XML 및 JSON 을 파싱할때 가장 중요한 요점은 바로 접근 입니다.
해당 요소 까지 접근을 해서 데이터를 반환 해야 한다는 점이 키포인트 입니다.
원하는 데이터를 반환하기 위해서 rss - channel 요소 까지 접근해야 합니다.
그리고 title, link, description... 과 같은 요소는 같은 위치에 있기 때문에 하위로 접근할 필요가 없습니다.
이렇게 요소에 접근하기 위해 사용하는 메서드가 Element 메서드 입니다.
<그림> Load 정적 메서드를 통해 구성한 XML 요소
그렇다면, 기본적으로 Load 되어 XElement 객체를 구성한 요소를 channel 까지 접근하기 위해선 어떻게 해야 할까요?
root.Element("channel")
위 처럼 구성된 노드에 하위 channel 요소에 접근하게 되었습니다.
이제 또 title, link, total... 등 요소에 접근하려면 어떻게 할까요?
root.Element("channel").Element("title")
root.Element("channel").Element("link")
이런식으로 접근 하시면 됩니다.
참고로 알아야 할 사항은 title, link, total 같은 요소는 XML 구조를 보시면 아시겠지만 모두 동일한 위치에 있습니다.
따라서, 꼭 먼저 title - link ... 순으로 접근할 필요가 없습니다.
root.Element("channel").Element("title") 을 써서 데이터 요소를 확인해 보면
<그림> root.Element("channel").Element("title") 반환 요소
<title></title> 같은 태그요소 까지 출력이 됩니다.
파싱하고 싶은 데이터는 <title></title> 태그요소 안에 있는 데이터 입니다.
이런 데이터만 반환 해주는 인스턴스 변수가 이미 XElement 클래스의 Value 라는 네임으로 선언이 되어있습니다.
root.Element("channel").Element("title").Value 를 출력해보면 아래와 같습니다.
<그림> root.Element("channel").Element("title").Value 반환 데이터
이렇게 원하는 데이터를 반복해서 파싱하시면 됩니다.
참고로 XML 에서 리턴 받은 데이터는 모두 string 형태 입니다.
따라서, 정수 및 실수는 반드시 변환 해주셔야 합니다.
다음은, 반복적인 item 요소를 파싱하는 방법을 배워보겠습니다.
먼저 소스코드를 먼저 봐주시기 바랍니다.
//반복적인 item 노드를 컬렉션 형태로 반환 IEnumerable<XElement> items = root.Element("channel").Descendants("item"); //LINQ 를 사용하여 item의 하위 노드를 파싱후 반환하여 List 로 반환 naver.item = items.Select((s) => { Item item = new Item(); item.title = s.Element("title").Value; item.link = s.Element("link").Value; item.image = s.Element("image").Value; item.author = s.Element("author").Value; item.price = s.Element("price").Value.Equals("") ? 0 : Convert.ToDouble(s.Element("price").Value); item.publisher = s.Element("publisher").Value; item.pubdate = s.Element("discount").Value.Equals("") ? 0 : Convert.ToInt32(s.Element("pubdate").Value); item.isbn = s.Element("isbn").Value; item.discount = s.Element("discount").Value.Equals("") ? 0 : Convert.ToInt32(s.Element("discount").Value); return item; }).ToList(); return naver; }
파싱되는 구조는 동일하지만, 반복적인 item 요소들을 한번에 컬렉션 형태로 바꿔줄 수 있습니다.
Element 로 단일로 접근해서 파싱하는 것이 아니라,
Descendants 메서드를 사용하여 tiem 요소를 반복적인 XElement 객체로 만들 수 있습니다.
그리고 나서, LINQ 를 사용하여 해당 요소를 파싱하고 List<Item> 형태로 반환하면 객체를 맵핑 할 수 있습니다.
[완성된 파서 객체]
class NaverParser { //private const string url = "http://naver.xml"; private string url = "naver.xml"; public NaverObject getNaverObject() { //XElement 요소 Load XElement root = XElement.Load(url); //맵핑할 객체 생성 NaverObject naver = new NaverObject(); //xml 요소 파싱후 객체에 저장 naver.title = root.Element("channel").Element("title").Value; naver.link = root.Element("channel").Element("link").Value; naver.description = root.Element("channel").Element("description").Value; naver.lastBuildDate = root.Element("channel").Element("lastBuildDate").Value; naver.total = root.Element("channel").Element("total").Value.Equals("") ? 0 : Convert.ToInt32(root.Element("channel").Element("total").Value); naver.start = root.Element("channel").Element("start").Value.Equals("") ? 0 : Convert.ToInt32(root.Element("channel").Element("start").Value); naver.display = root.Element("channel").Element("display").Value.Equals("") ? 0 : Convert.ToInt32(root.Element("channel").Element("display").Value); //반복적인 item 노드를 컬렉션 형태로 반환 IEnumerable<XElement> items = root.Element("channel").Descendants("item"); //LINQ 를 사용하여 item의 하위 노드를 파싱후 반환하여 List 로 반환 naver.item = items.Select((s) => { Item item = new Item(); item.title = s.Element("title").Value; item.link = s.Element("link").Value; item.image = s.Element("image").Value; item.author = s.Element("author").Value; item.price = s.Element("price").Value.Equals("") ? 0 : Convert.ToDouble(s.Element("price").Value); item.publisher = s.Element("publisher").Value; item.pubdate = s.Element("discount").Value.Equals("") ? 0 : Convert.ToInt32(s.Element("pubdate").Value); item.isbn = s.Element("isbn").Value; item.discount = s.Element("discount").Value.Equals("") ? 0 : Convert.ToInt32(s.Element("discount").Value); return item; }).ToList(); return naver; } }
[결과]
자바로 DOM 파서를 구현해서 사용한다면 정말 끔찍할 정도로 소스코드가 복잡해지지만, (구현 해보신분들 알거에요)
C#은 이렇게 간단하게 XML 을 파싱할 수 있습니다.
LINQ는 정말 닷넷의 최고의 기능인거 같습니다.
[참고] XElement 클래스 - MSDN
http://msdn.microsoft.com/ko-kr/library/Bb340098(v=vs.110).aspx
'프로그래밍 > C#' 카테고리의 다른 글
using 을 통한 리소스 해제 기법 (0) | 2014.12.02 |
---|---|
JSON Text 파싱 방법 (0) | 2014.12.01 |
JavaScriptSerializer 를 통한 JSON Serialization (0) | 2014.11.30 |