본문 바로가기

프로그래밍/C#

LINQ 를 통한 C# XML 파싱 (LINQ to XML)

 

 

이번 포스팅에서는 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;
        }

    }

 

 

[결과]

 

 

 

 

 

naver.xmlParser.Console.zip

 

 

 

자바로 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