[Simple XML 파서 만들기] #1. 파싱 원리 그(it) 얘기

들어가기 전에...

전에 진행했던 'XML 파서 만들기'는 내용이 너무 부실하다고 생각되서 이번 편부터 재구성해서 다시 올려봅니다. 제 나름대로는 머리를 쥐어 짜면서 설명해 보려고 했습니다만, 기본적으로 본 강좌의 내용은 초보자를 대상으로 하지 않습니다. 적어도 C++ 정도의 코드는 읽을 수 있어야 하고, 디자인 패턴에 대해서도 조금은 알고 있어야 합니다. 그리고 텍스트 파일 처리에 대한 경험도 있는 게 좋습니다.


요즘에는 기본적으로 제공되는 라이브러리에 XML 파서를 포함하고 있는 추세라서, 아마도 직접 XML 파서를 제작할 필요성은 그다지 없어 보입니다.

하지만, 복잡하고 방대한 XML 데이터를 처리하는게 아니라, 아주 간단한 설정사항 정도를 담는 XML을 처리하는 것뿐이라면 굳이 무거운 파서를 이용할 필요가 없을 겁니다. 복잡한 XML의 규약을 전부 해석하지 않고, 정말 필요한 부분만 골라서 처리할 수 있는 단순한 파서... 그런 생각에서 자작 XML 파서에 도전해 봤습니다.


우선, XML 데이터 형식에 대해 간단히 집고 넘어가 보죠.

HTML 코드를 많이 봤다면 한눈에 들어올 수 있을 겁니다. 사실 여기서 구현하려는 파서 역시 HTML 수준에서 크게 차이가 나지 않습니다. 여기서는 오히려 HTML 파싱보다도 더욱 단순화 해서 생각하고 있습니다.


복잡하게 생각할 것 없이 < > 안에 있는 것은 무조건 '태그(Tag)'라는 단위로 봅니다. 그리고 태그는 기본적으로 여는 태그와 닫는 태그의 쌍으로 구성됩니다. 닫는 태그 쪽에는 태그 이름 앞에 '/' 기호를 붙여서 구분합니다. 즉 < > ~ </ > 형태가 기본꼴입니다.

여는 태그와 닫는 태그 사이의 공간에는 그 태그에 포함되는 데이터가 기술됩니다. 이 경우 태그 안에 다시 태그를 포함하는 것도 가능합니다. 즉, 데이터가 들어갈 자리에 다시 < > ~ </ > 형태로 집어넣을 수 있습니다.



하지만 <A><B> ... </B></A> 형태로 중첩시키는 것은 가능하지만, <A><B></A><B> 이런식으로 서로 범위가 엇갈리는 것은 XML에서는 허용되지 않습니다.

그리고 여는 태그 쪽에서는 '속성(Attribute)'이라는 부가적인 정보를 기술할 수 있습니다. 하나의 태그는 한번에 한개의 데이터만 가질 수 있지만, 속성은 이름="값" 형식으로 몇 개든지 열거할 수 있습니다.

하지만, 속성은 필수 사항은 아니기 때문에 태그의 성격에 따라서는 쓰지 않아도 됩니다.



하지만 데이터 역시 선택사항 입니다. 그래서 경우에 따라서는 속성 정보만 가지고 있는 태그도 있을 수 있습니다. 이런 식으로 태그에 데이터가 존재하지 않을 경우에는 다음과 같이 <Tag/> 형식으로 기술할 수 있습니다.



우리가 파싱할 XML의 스펙에 대해서는 우선 이정도만 알아도 충분합니다. (사실 저도 그리 자세히는 모릅니다...)
그럼 지금부터는 XML 텍스트를 파싱하는 알고리즘에 대해 고민해 보기로 합니다.
일단, XML의 파싱과정은 크게 '태그 파싱'과 '데이터 파싱'으로 나눌 수 있습니다. 그런데 문제는 여기서 '태그 파싱' 과정이 좀 더 복잡하다는 겁니다. 그래서 '태그 파싱'은 다시 다음과 세부 과정으로 나눠볼 수 있습니다.

    [태그 파싱]
        * 태그 이름 파싱
        [속성 파싱]
             * 속성 이름 파싱
             * 속성 값 파싱

그래서 최종적으로 우리가 생각해야될 것은 '태그 이름 파싱', '속성 이름 파싱', '속성 값 파싱', '데이터 파싱'의 4가지 과정입니다. 이것을 상태 다이어그램으로 표현해 보며 다음과 같습니다.
이 상태 다이어그램을 보면서 XML을 파싱하는 절차를 생각해 봅시다.

1: 먼저 [데이터 파싱]과정 부터 시작합니다.
   - 이 과정은 비교적 단순합니다. 한글자씩 읽어서 버퍼에 계속 쌓아두면 됩니다.

   1-1: 태그를 여는 문자(<)가 발견되면 [태그 이름 파싱] 과정으로 넘어갑니다.

2: 공백이나 태그의 끝을 의미하는 기호('/', '>'  등)가 나올때까지 텍스트를 읽어들입니다.
   - 최초에 등장하는 공백 문자들은 무시합니다.

   2-1: 태그를 닫는 문자(>)가 발견되면 [데이터 파싱] 과정으로 돌아갑니다. (즉, 1번으로 돌아감)
   2-2. 공백 문자가 발견되면 [속성 이름 파싱] 과정을 진행합니다.

3: '=' 기호나 공백, 혹은 '/', '>' 등의 문자가 발견될 때까지 텍스트를 읽습니다.
   3-1: 태그를 닫는 문자(>)가 발견되면 [데이터 파싱] 과정으로 돌아갑니다. (1번으로...)
   3-2: '=' 기호가 발견되면 [속성 값 파싱] 과정으로 넘어갑니다.
   3-3. 공백 문자가 발견되면 이것은 값 없이 속성 이름만 존재하는 경우입니다.
         현재의 속성이름을 저장한 다음, 지금의 과정을 계속 진행합니다.

4: " " 사이의 문자열을 읽어들입니다. 그러고 나서...
   4-1: 태그를 닫는 문자(>)가 발견되면 [데이터 파싱] 과정으로 돌아갑니다. (1번으로...)
   4-2: 그 외의 경우는 [속성 이름 파싱] 과정으로 되돌아갑니다. (3번으로...)

아직 좀 더 손봐야 할 부분이 있기는 하지만, 대략적인 파싱 알고리즘은 이정도의 구도로 잡으면 되겠습니다.

그런데 한가지 중요한 문제가 있으니, 바로 파싱되는 데이터를 어떤 식으로 저장할 것인가 입니다. MS에서 제공하는 DOM 파서의 경우는  파싱을 수행하면서 그때 그때 트리(Tree) 형태로 자료구조를 만듭니다. 우리도 그런식으로 처리해 볼 수도 있겠지만, 저는 이 좀 더 단순한 방법을 써보기로 했습니다.

저는 자바(JAVA)에서 제공하는 SAX(Simple API for XML) 파서의 방식을 응용해 봤습니다. 이 파싱 방법의 특징은 이벤트(Event) 기반이라는 점 입니다. 일반적인 DOM 파서의 경우는 XML 데이터를 파싱하면서 태그들의 구조를 Tree 형식으로 구축하게 됩니다만, SAX의 경우는 그냥 XML 문서를 쭉 읽어들이면서 태그(< ... >)를 만날 때마다 Event만 발생시킵니다.

간단히 예를 들어보겠습니다.

다음 문장을 파싱해야 된다고 할 때,

        <A href="http://sizuha.elgoos.com">Sizuha's Blog</A>

우선 여는 태그 부분을 먼저 파싱하게 됩니다.

        <A href="http://sizuha.egloos.com">Sizuha's Blog</A>

그 후에는 아래처럼 OnTag 이벤트를 발생시킵니다.

        OnTag ( tag name: "A", attributes: ("href", "http://sizuha.egloos.com") )

이벤트 핸들러는 '태그 이름(tag name)'하고 '속성(attributes)'을 인수로 넘겨받게 되고, 이벤트를 전달 받는 쪽에서는 이 정보를 바탕으로 알아서 처리해 주면 됩니다.

그 다음은 데이터를 파싱하게 되겠죠.

        <A href="http://sizuha.egloos.com">Sizuha's Blog</A>

이번에는 간단하게 OnData 이벤트를 발생시키면 됩니다.

        OnData ( "Sizuha's Blog" )

이제 남은 건 하나군요.

        <A href="http://sizuha.egloos.com">Sizuha's Blog</A>

이건 태그 이름이 / 로 시작하기 때문에, 닫는 태그로 인식해야겠지요.
따라서 이벤트도 OnCloseTag 가 됩니다.

        OnCloseTag ( tag name: "A" )

이 과정을 계속 반복하면서 부지런히 이벤트 핸들러에게 자료를 넘겨 주기만 하면 기본적인 파서가 구현됩니다. 물론 진짜 파서를 구현하기 위해서는 몇가지 예외적인 상황도 고려해야 합니다.

대표적으로, XML에서는 이런 식의 태그도 가능하죠.

        <IMG src=" ........ " />

이렇게 포함되는 데이터 없이 태그 하나로 그냥 종결되는 경우도 처리를 해야합니다.
그래서 저는 이런 경우를 고려해서 OnTag 이벤트 부분이 이렇게 수정했습니다.

        OnTag ( TagName, Attributes, isContainer )

마지막에 추가된 인수(isContainer)가 true로 넘어오면 이 태그가 <...> ~ </...> 형태라는 것이고, false일 경우는 <.../> 형태라고 구분할 수 있습니다.


다음 편에서는 위 내용들을 실제로 구현한 C++ 코드를 보여드리고, 몇가지 예외사항과 부수적인 기능을 포함해서 알고리즘을 보완해 보기로 하겠습니다.

핑백

  • the elegant anivurse : [Simple XML 파서 만들기] #2. 준비 작업 2008-08-01 23:59:56 #

    ... 서 연재(?)를 계속한다고 해도 의미가 있을지는 모르겠지만, 그래도 미완성으로 남겨둘 수는 없기에 늦었지만 계속 이어나가겠습니다. 관련글: [Simple XML 파서 만들기] #1. 파싱 원리 이번 건 XML 파싱과는 크게 상관은 없지만, 본격으로 XML 파서를 만들기 전에 먼저 필요한 것들을 점검해 보겠습니다. &nbsp ... more

  • the elegant anivurse : [Simple XML 파서 만들기] #3. Parser State Pattern 2008-08-02 01:29:34 #

    ... [관련글] [Simple XML 파서 만들기] #1. 파싱 원리 [Simple XML 파서 만들기] #2. 준비 작업 이제부터 1편에서 나왔던 바로 이 그림을 그대로 '스테이트 패턴( ... more

  • the elegant anivurse : Simple XML 파서 소스 & Xml Tree Viewer 2008-08-02 12:21:54 #

    ... 현재 감질나게(···) 진행중인 「Simple XML 파서 만들기」에 관한 글을 올리고 있습니다만, 이게 어느 새월에 끝날지 모르겠고 도저히 못기다리겠다라든가, 혹은 닥치고 소스나 내뱉었으면 좋겠다는 분도 혹시(...) 있을지도 ... more

덧글

  • 로키 2007/10/28 16:48 # 답글

    c++를 몰라요...................
    컴 공과 갈려는데 내후년이면 이말을 알아들을수 있기를!
  • CECRI 2007/10/29 16:40 # 삭제 답글

    전에 올린 포스팅으로도 사실 state 패턴에 대해 꽤 많은것을 배워 갈 수 있었습니다만.. 설명이 자세하면 저는 좋습니다.^^
  • NamMoo 2007/11/14 10:12 # 답글

    컴공가지마요 ㅠ.ㅠ
    3D 업종이에요...ㅠ.ㅠ
    교수라도 되려면 모를까....ㅠ.ㅠ
    향후, 10년? 정도면 우리나라도...
    일본, 미국처럼.. 못사는 나라의 외국인 프로그래머들이,
    들어와서 일해줄겁니다....
    왜냐구요...다들 힘든일은 안하려하니까...
    그말은...지금 IT 업계가 느무 힘들다는 말이죠...
    잘 생각해봐요...ㅠ.ㅠ
  • NamMoo 2007/11/14 10:12 # 답글

    아....그냥 제 생각이에요...;;;;
  • 피스 2008/06/23 18:39 # 삭제 답글

    좋은글 감사합니다
  • HK IN 2008/12/30 14:37 # 삭제 답글

    검색하다가 들어왔는데요 정말 도움이 많이 되었습니다.

    감사합니다.
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.