본문 바로가기
개발

XmlLite 을 이용한 메뉴 XML 로딩하기

by esstory 2008.04.16

 

윈도우 어플리케이션의 설정을 저장하기 위해 윈도우 3.1 시절부터 널리 사용되어온 방법은


GetPrivateProfileInt

GetPrivateProfileString/WritePrivateProfileString


함수를 이용하여 .ini 파일에 설정을 불러오고 저장하는 방식이었습니다.

                                        

워낙 간단하게 파일에 설정을 읽고 쓸 수 있기 때문에 아직까지도 많은 프로그램에서 이 방식을 선호하고 있습니다.

하지만 위 함수의 MSDN 도움말을 보면 이 함수는 16비트 하위호환성을 제공하기 위해 제공될 뿐 설정은 레지스트리에 저장하도록 권장(Should 는 권장이라기 보다 좀 더 의미가 강할 거 같습니다만) 하고 있습니다.

 

Note  This function is provided only for compatibility with 16-bit Windows-based applications. Applications should store initialization information in the registry

 

그래서 .ini 파일에

[setting]

POSITON  = 1

 

과 같이 간단한 KEY = VALUE 식으로 저장하던 방식에서 벗어나 윈도우 레지스트리를 적극적으로 활용하기 시작했습니다.

개인적으로는 윈도우 레지스트리에 정보를 저장하고 불러오는 방식을 선호하는 편입니다.

왜냐하면 프로그램의 설정을 ini 파일과는 달리 계층적으로 관리할 수 있게 해 주어서 이전보다 훨씬 복잡한 설정을 저장할 수 있게 해 주기 때문입니다. 또한 저장할 수 있는 Value 에는 텍스트, 숫자, 바이너리 등으로 어플리케이션 성격에 맡게 선택의 폭도 커졌고, 설정의 저장과 로딩에 대한 부담도 대부분 윈도우가 알아서 처리하기 때문에 파일 유지 보수에 대한 부담도 덜 수 있습니다.

윈도우 레지스트리에 정보를 저장하는 방법은 아래와 같은 간단한 몇 줄의 공통함수 하나면 됩니다.  아래 코드는 윈도우 레지스트리에 특정 문자열 값을 저장하는 코드입니다. (CHKey HKEY Wrapping 한 간단한 클래스)

inline bool WriteRegString(HKEY hRoot,const char* szDir, const char* szKeyName, const char* szValue)

{

           CHKey hKey;

           DWORD dwDisposition;

 

           long lRet =RegCreateKeyEx(hRoot,szDir,0,"" , 0,KEY_ALL_ACCESS,NULL,&hKey,&dwDisposition);

 

           if (lRet != ERROR_SUCCESS)

           {

                     return false;

           }

 

           if  (RegSetValueEx(hKey,szKeyName,0,REG_SZ,(LPBYTE)szValue, DWORD(strlen(szValue) + 1)) )

           {

                     return false;

           }

 

           return true;

}


윈도우 레지스트리의 단점이라면, 사용자의 레지스트리를 어지럽힌다는 점, 특히 설치된 어플리케이션을 삭제할 때 신경써서 지워주지 않으면 윈도우에 영원토록 쓰레기로 남을 수 있다는 점, 그래서 윈도우를 점점 무겁게 만드는 주범으로 지목받는 점 등이 있습니다. , 사용자 설정을 다른 PC 로 옮겨야 할 때 일반적인 ini 파일이라면 단순히 파일을 복사하면 되지만 레지스트리는 좀더 복잡한 과정을 거쳐야만 한다는 점입니다.

 

어느 방법이던 개발자가 선호하는 방식으로 사용하면 되겠지요.

몇 년 전부터는 xml 을 설정으로 활용하는 사례가 늘었습니다.

XML INI 파일의 장점과 윈도우 레지스트리의 계층적 설정 저장 접근 방식의 장점을 같이 가지고 있다는 장점이 있습니다.

아시는것처럼 XML Attribute 를 통해 확장성이 훌륭하기 때문에 윈도우 레지스트리와 비슷한 효과를 볼 수 있고, 텍스트 기반이라 데이터를 눈으로 확인하기도 쉬운 편입니다.

하지만 XML 파일을 열고 닫기 위해서는 제대로 된 XML PARSER 가 있어야 했습니다.

초창기부터 개발자들 사이에 많이 사용되는 XML PARSER로는 MSXML PARSER 가 있습니다. 이 파서는 XML에 대한 충분한 지식없이도 COM Interface 와 샘플 프로그램 몇 개 익히면 간단하게 사용할 수 있다는 장점이 있었습니다.


MSXML2::IXMLDOMNodePtr                             

MSXML2::IXMLDOMNodeListPtr              

MSXML2::IXMLDOMNodePtr                             

MSXML2::IXMLDOMNamedNodeMapPtr    

MSXML2::IXMLDOMNodeListPtr              

MSXML2::IXMLDOMNodePtr                             

MSXML2::IXMLDOMNamedNodeMapPtr    

MSXML2::IXMLDOMNodePtr                             

….


개인적으로 MSXML 정도면 충분하다고 생각합니다만 몇몇 분야에서는 적합하지 않을 수 있습니다. 저희 회사 같은 경우 사용자 PC 마다 다양하게 설치된 MSXML 버전 때문에(MSXML은 별도 배포버전이 있지만 대부분 사용자들은 IE 를 설치할 때 같이 배포된 MSXML 을 사용했기 때문에 사용자의 IE 버전과 OS 버전에 따라 여러 가지 버전이 설치된 경우가 많았습니다.) 정상적으로 파싱을 하지 못하는 경우가 있었고 이에 대한 대책이 필요했었습니다.

제가 생각한 방법은 초간단 XML PARSER 를 만드는 것이었습니다. 복잡한 XML 은 분석하지 못하더라도 일반적으로 자주 사용되는 구문에 대한 해석만 가능한 파서를 만들어서 XML 형식으로 어플리케이션의 설정을 저정/불러올 수 있도록 개발했습니다.

지금 생각해도 XML PARSER 를 만드는 것도 어려움이 있지만, XML 파일에 들어 있는 수많은 XML Encoding 을 제대로 해석하는 부분은 더 어려운 것 같습니다. 그래서 제가 만든 파서는 이런 부분은 건너띄고 <> </>  로 끝나는 계층 구조를 따라 들어가는 간단한 파싱 트리였지요.

 

이제 본론으로 들어갑니다.

좀 오래된 내용이지만 작년 4월에 MSDN 에 좋은 기사가 올라왔습니다.


네이티브 C++ 위한 작고 빠른 XML 파서

 

XmlLite 를 소개하는 기사인데요. 기사가 워낙 설명이 잘되어 있어 위 기사에 있는 내용과 샘플을 보면 XmlLite 를 통한 XML 파일 파싱에 대해 개념을 잡는데 충분합니다.

 

제가 생각하는 XmlLite 의 장점은 이렇습니다.

일단 가볍습니다. 배포가 필요한 XmlLite.dll 파일의 크기는 약 120KB 정도로 작습니다.

빠릅니다.  MSXML 의 경우 버전도 여러 버전인데다 기능도 많아져서 어딘지 무겁다는 느낌이지만 XmlLite 는 최소 기능만 제공하기 때문에 작고 가볍습니다.

COM 은 아니지만 COM가 같은 방식으로 인터페이스를 전달하기 때문에 COM에 익숙한 개발자라면 쉽게 접근할 수 있습니다.

 

단점이라면

MSXML에 비해 제공되는 기능이 작습니다.(아주 작습니다^^) 작을 뿐만 아니라 거의 XML 을 한 줄씩 읽어서 원하는 내용을 취하는 형태입니다. 그러다 보니, 파싱을 위한 코딩 량이 기존에 비해 상당히 커질 수 있습니다.

 

이제부터는 XmlLite 를 이용해서 간단하게 XML 파일을 읽어 메모리에 로딩하는 샘플을 가지고 설명 드리겠습니다.

XmlLite 를 테스트로 삼을 XML 파일은 아래와 같습니다. IE6의 메뉴를 간단한 XML로 표현했고 이를 프로그램에서 읽어 들여 자체 메모리를 구성하는 과정을 설명하고자 합니다.

 

<?xml version="1.0" encoding="utf-8" ?>

<IE6_MENU>

  <MENU title="파일" submenu ="true">

    <MENU title ="새로만들기"></MENU>

    <MENU title ="열기"></MENU>

    <MENU title ="편집"></MENU>

    <MENU title ="저장"></MENU>

    <MENU title ="다른 이름으로 저장"></MENU>

    <MENU title ="" Seperator ="true"></MENU>

    <MENU title ="페이지 설정"></MENU>

    <MENU title ="인쇄"></MENU>

    <MENU title ="인쇄 미리보기"></MENU>

    <MENU title ="" Seperator ="true"></MENU>

    <MENU title ="보내기" submenu ="true">

      <MENU title ="전자메일로 페이지 보내기"></MENU>

      <MENU title ="전자 메일로 링크보내기"></MENU>

      <MENU title ="바탕화면에 바로가기 만들기 "></MENU>     

    </MENU>

    <MENU title ="가져오기 및 내보내기..."></MENU>

    <MENU title ="" Seperator ="true"></MENU>

    <MENU title ="속성"></MENU>

    <MENU title ="오프라인으로 작업"></MENU>

    <MENU title ="닫기"></MENU>

  </MENU>

  <MENU title = "편집" submenu ="true">

    <MENU title ="잘라내기"></MENU>

    <MENU title ="복사"></MENU>

    <MENU title ="붙여넣기"></MENU>

    <MENU title ="" Seperator ="true"></MENU>

    <MENU title ="모두 선택"></MENU>

    <MENU title ="" Seperator ="true"></MENU>

    <MENU title ="이 페이지에서 찾기" submenu="true">

      <MENU title ="잘라내기"></MENU>

      <MENU title ="복사"></MENU>

      <MENU title ="붙여넣기"></MENU>

    </MENU>

  </MENU>

</IE6_MENU>


위 파일을 담을 메모리 구조체는 다음과 같이 정의했습니다. 각각의 메뉴는 하위 메뉴 리스트를(vector<MENU_ELMT>* pMenu) 가질 수도 있어서 이 부분은 포인터로 만들었습니다.


namespace XML_MENU

{

           struct MENU_ELMT

           {

                     wstring sTitle;

                     bool bSubMenu;                      // 하위메뉴보유여부

                     bool bIsSeperator;                    // Seperator 인지여부

                     vector<MENU_ELMT>* pMenu;   // 하위메뉴가있을경우에만유효

 

                     MENU_ELMT()

                     {

                                bSubMenu = false;

                                bIsSeperator = false;

                                pMenu = NULL;

                     }

           };

                    

};


실제 모든 메뉴들은 다음과 같은 vector 에 저장되어 있습니다.

vector<XML_MENU::MENU_ELMT> m_vtMenus;

 

XML 파일로부터 IXmlReader 인터페이스를 가져오는 코드는 아래와 같습니다.

bool CXMLMenu::ReadMenuXMLFile(const TCHAR* lpFile)

{

           USES_CONVERSION;

           ClearMenuElment(&m_vtMenus, true);

           CComPtr<IStream> stream;

           CComPtr<IXmlReader> pReader;

          

           ::SHCreateStreamOnFile(lpFile,

                     //STGM_READ | STGM_WRITE | STGM_SHARE_DENY_WRITE,

                     STGM_READ,

                     &stream);

 

           CreateXmlReader(__uuidof(IXmlReader), (void**) &pReader, NULL);

           pReader->SetProperty(XmlReaderProperty_DtdProcessing, DtdProcessing_Prohibit);

           pReader->SetInput(stream);

 

           return ReadXMLFile(pReader, &m_vtMenus);

 

}


예제에서 보는 것처럼

CComPtr<IXmlReader>

와 같이 ATL 에 있는 CComPtr<> 을 사용해서 리더 인터페이스를 선언합니다. 실제 XmlLite COM 방식은 아니지만, 인터페이스를 구하고, 사용하는 방식은 마치 COM 을 사용하는 방식으로 되어 있습니다.  COM 의 경우 XmlLite 의 등록문제가 있기 때문에 이 부분을 포기하고(아마도 비스타의 영향이 큰 듯), 사용하는 방식만 가져다 쓴 것으로 보입니다.

XML 파일에서 SHCreateStreamOnFile 함수를 사용하여 IStream 인터페이스를 구하고 CreateXmlReader 에서 생성한 IXmlReader Input 으로 IStream 을 전달하면 XML 파일 읽기작업을 시작할 수 있습니다.

 

ReadXMLFile() 함수는 XML 내부에서 하위 메뉴가 계속해서 같은 형식으로 나타날 수 있어 재귀적으로 호출되도록 만들어졌습니다.

 

bool CXMLMenu::ReadXMLFile(IXmlReader* pReader, vector<XML_MENU::MENU_ELMT>* pMenu)


// XML 파일로부터값을읽어들여메뉴배열(vector) 에추가한다.

// IXmlReader* pReader - XmlLite 리더인터페이스

// vector<XML_MENU::MENU_ELMT>* pMenu - 읽어들인정보를저장하는메뉴포인터

bool CXMLMenu::ReadXMLFile(IXmlReader* pReader, vector<XML_MENU::MENU_ELMT>* pMenu)

{

           if (pReader == NULL)

                     return false;

           if (pMenu == NULL)

                     return false;

 

           XmlNodeType nodeType;

           const WCHAR* pwszPrefix;

           const WCHAR* pwszLocalName;

           const WCHAR* pwszValue;

           UINT cwchPrefix;

           map<wstring, wstring> mapAttibutes;

 

           HRESULT hr;

           bool bOpen = false;

           while (S_OK == pReader->Read(&nodeType))

           {

      switch (nodeType)

             {

             case XmlNodeType_XmlDeclaration:

                       TRACE(L"XmlDeclaration\n");

                       if (false == GetXMLAttributes(pReader, &mapAttibutes))

                       {

                                  //TRACE(L"Error writing attributes, error is %08.8lx", hr);

                                  return false;

                       }

                       break;

             case XmlNodeType_Element:

                        {

                                  bOpen = true;

                                  if (FAILED(hr = pReader->GetPrefix(&pwszPrefix, &cwchPrefix)))

                                  {

                                            TRACE(L"Error getting prefix, error is %08.8lx", hr);

                                            return false;

                                  }

                                  // 현재엘리먼트의local name 가져오기

                                  if (FAILED(hr = pReader->GetLocalName(&pwszLocalName, NULL)))

                                  {

                                            TRACE(L"Error getting local name, error is %08.8lx", hr);

                                            return false;

                                  }

                                  if (cwchPrefix > 0)

                                            TRACE(L"Element: %s:%s\n", pwszPrefix, pwszLocalName);

                                  else

                                            TRACE(L"Element: %s\n", pwszLocalName);

 

                                  if (_tcscmp(pwszLocalName, _T("MENU")) != 0)

                                            continue;

 

                                  // element 의속성이있을경우속성을표시

                                  if (false == GetXMLAttributes(pReader, &mapAttibutes))

                                  {

                                            TRACE(L"Error writing attributes, error is %08.8lx", hr);

                                            return false;

                                  }

 

                                  XML_MENU::MENU_ELMT elmt;

                                  //elmt.sTitle = pwszLocalName;

                                 

                                  wstring sValue;

                                  GetValue(&mapAttibutes, _T("title"), sValue);

                                  elmt.sTitle = sValue;

 

                                  GetValue(&mapAttibutes, _T("submenu"), sValue);

                                  // sub menu 인가?

                                  if (sValue == _T("true"))

                                  {

                                            elmt.bSubMenu = true;

                                            elmt.pMenu = new vector<XML_MENU::MENU_ELMT>;

                                  }

                                  // Seperator 인가?

                                  GetValue(&mapAttibutes, _T("Seperator"), sValue);

                                  if (sValue == _T("true"))

                                  {                                        

                                            elmt.bIsSeperator = true;

                                  }

                                  pMenu->push_back(elmt);

                                  // Sub Menu 가있을경우재귀적으로ReadXMLFile() 를다시호출해서SubMenu 를구성한다.

                                  if (elmt.bSubMenu && elmt.pMenu)

                                            ReadXMLFile(pReader, elmt.pMenu);

 

                                                    

                                  if (pReader->IsEmptyElement() )

                                            TRACE(L" (empty)");

                       }

                       break;

             case XmlNodeType_EndElement:

                       if (FAILED(hr = pReader->GetPrefix(&pwszPrefix, &cwchPrefix)))

                       {

                                  TRACE(L"Error getting prefix, error is %08.8lx", hr);

                                  return false;

                       }

                       if (FAILED(hr = pReader->GetLocalName(&pwszLocalName, NULL)))

                       {

                                  TRACE(L"Error getting local name, error is %08.8lx", hr);

                                  return false;

                       }

                       if (cwchPrefix > 0)

                                  TRACE(L"End Element: %s:%s\n", pwszPrefix, pwszLocalName);

                       else

                                  TRACE(L"End Element: %s\n", pwszLocalName);

                       if (bOpen)

                                  bOpen = false;

                       else

                                  return true;

                       break;

             case XmlNodeType_Text:

             case XmlNodeType_Whitespace:

                       if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))

                       {

                                  TRACE(L"Error getting value, error is %08.8lx", hr);

                                  return false;

                       }

                       if (_tcslen(pwszValue) >= sizeof(TCHAR))

                                TRACE(L"Text: %s\n", pwszValue);

                       break;

             case XmlNodeType_CDATA:

                       if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))

                       {

                                  TRACE(L"Error getting value, error is %08.8lx", hr);

                                  return false;

                       }

                       TRACE(L"CDATA: %s\n", pwszValue);

                       break;

             case XmlNodeType_ProcessingInstruction:

                       if (FAILED(hr = pReader->GetLocalName(&pwszLocalName, NULL)))

                       {

                                  TRACE(L"Error getting name, error is %08.8lx", hr);

                                  return false;

                       }

                       if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))

                       {

                                  TRACE(L"Error getting value, error is %08.8lx", hr);

                                  return false;

                       }

                       TRACE(L"Processing Instruction name:%S value:%S\n", pwszLocalName, pwszValue);

                       break;

             case XmlNodeType_Comment:

                       if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))

                       {

                                  TRACE(L"Error getting value, error is %08.8lx", hr);

                                  return false;

                       }

                       TRACE(L"Comment: %s\n", pwszValue);

                       break;

             case XmlNodeType_DocumentType:

                       TRACE(L"DOCTYPE is not printed\n");

                       break;

             }

           }

           return true;

}

 


사실 이 함수의 대부분의 코드는 MSDN 샘플 코드를 재사용했습니다. 메뉴 XML 파일에 특화된 부분만 굵은 글씨로 표시했습니다.

Attribute 를 가져와 속성을 분석하는 코드는 다음과 같습니다.

XmlLite 에서는 속성들 각각에 대해서 Key(GetLocalName) Value(GetValue) 을 넘겨주는 데 GetXMLAttributes()는 이 값을 map<, > 에 하나씩 추가하는 함수입니다.


// 현재Elemenet 에대한Attributes 를가져와map <, > 으로저장한다.

bool CXMLMenu::GetXMLAttributes(IXmlReader* pReader, map<wstring, wstring>* pmapData)

{

           if (pmapData == NULL)

                     return false;

           pmapData->clear();

 

    const WCHAR* pwszPrefix;

    const WCHAR* pwszLocalName;

    const WCHAR* pwszValue;

    HRESULT hr = pReader->MoveToFirstAttribute();

 

    if (S_FALSE == hr)

        return true;

    if (S_OK != hr)

    {

        TRACE(L"Error moving to first attribute, error is %08.8lx", hr);

        return false;

    }

    else

    {

        while (TRUE)

        {

                                // attribute 가있는경우에만처리

            if (!pReader->IsDefault())

            {

                UINT cwchPrefix;

                                          // 리더가위치한곳의namespace prefix 정보를가져온다.

                if (FAILED(hr = pReader->GetPrefix(&pwszPrefix, &cwchPrefix)))

                {

                    TRACE(L"Error getting prefix, error is %08.8lx", hr);

                    return false;

                }

                                          // reader 가현재위치한노드의local name 을구한다.

                if (FAILED(hr = pReader->GetLocalName(&pwszLocalName, NULL)))

                {

                    TRACE(L"Error getting local name, error is %08.8lx", hr);

                    return false;

                }

                                          // 현재토큰의값을가져온다.

                if (FAILED(hr = pReader->GetValue(&pwszValue, NULL)))

                {

                    TRACE(L"Error getting value, error is %08.8lx", hr);

                    return false;

                }

 

                if (cwchPrefix > 0)

                    TRACE(L"Attr: %s:%s=\"%s\" \n", pwszPrefix, pwszLocalName, pwszValue);

                else

                    TRACE(L"Attr: %s=\"%s\" \n", pwszLocalName, pwszValue);

 

                                          if (pmapData->find(pwszLocalName) == pmapData->end())

                                          {

                                                     pmapData->insert(make_pair(pwszLocalName,pwszValue));

                                          }

 

            }

 

            if (S_OK != pReader->MoveToNextAttribute())

                break;

        }

    }

    return true;

}


위 두 함수를 가지고 XML 메뉴를 해석을 마칠 수 있었습니다.  이제 읽어온 데이터가 정확한 지 확인하는 작업이 남았습니다.

TraceAllMenu () 는 메뉴가 정상적으로 읽혀 졌는지 테스트 하는 함수입니다. TraceMenu() 는 역시 재귀적으로 호출하여 하위 메뉴로 따라 내려가면서 추적할 수 있도록 만들어 졌습니다.

// 메모리에저장된메뉴구조를아웃풋에TRACE 한다.

void CXMLMenu::TraceAllMenu()

{

           int nIndent = 0;

           TRACE("\n\n\n\n\n\n");

           TraceMenu(&m_vtMenus, nIndent);

}

 

void CXMLMenu::TraceMenu(vector<XML_MENU::MENU_ELMT>* pElment, int& nIndent)

{

           vector<XML_MENU::MENU_ELMT>::iterator it;

 

           for (it = pElment->begin(); it != pElment->end(); ++it)

           {         

                     // Depth 에따라들여쓰기로표시

                     for (int i = 0; i < nIndent; i++)

                                TRACE(_T("     "));

                     if (nIndent)

                                TRACE(_T("└"));

 

                     // Seperator 인경우

                     if (it->bIsSeperator)

                                TRACE(_T("____________________________\n"));

                     else

                                TRACE(_T("%s\n") , it->sTitle.c_str() );

 

                     // 하위메뉴가있는경우처리

                     if (it->bSubMenu && it->pMenu)

                     {

                                nIndent++;

                                TraceMenu(it->pMenu, nIndent);

                                nIndent--;

                     }

           }

}


Sub Menu 를 다루기 위해 포인터를 사용했기 때문에 깨끗하게 메모리를 해제하는 작업이 중요합니다.  마지막으로 포인터 정리하는 함수입니다.


void CXMLMenu::ClearMenuElment(vector<XML_MENU::MENU_ELMT>* pElment, bool IsFirst)

{

           vector<XML_MENU::MENU_ELMT>::iterator it;

 

           for (it = pElment->begin(); it != pElment->end(); ++it)

           {         

                     if (it->bSubMenu && it->pMenu)

                     {

                                ClearMenuElment(it->pMenu, false);

                     }

           }

           pElment->clear();

           if (!IsFirst)

                     delete pElment;

 

}

ClearMenuElment(&m_vtMenus, true);


이상으로 XmlLite 를 이용해서 간단한 XML 메뉴 파일을 읽어 메모리를 구성하는 예제를 살펴 봤습니다.

어플리케이션에서는 각자 특성에 맞게 다양한 설정을 읽어오고 저장할 수 있어야 합니다.

XML 파일은 어플리케이션의 설정을 저장하고 불러오는데 중요한 자료구조가 될 수 있고 XmlLite XML 파일의 Encoding에 신경 쓰지 않고 데이터 항목에 접근할 수 있는 방법을 제공합니다.

MSXML을 사용하는데 성능이나, 사용성에 문제가 없다면 굳이 XmlLite로 옮겨올 필요는 없을 듯 하구요. 범용적인 어플리케이션 작성과 속도가 빠른 XML 파일 처리에 관심이 있는 분들은 시도해 볼만 하다고 생각됩니다.


 

 

태그

,

댓글13

  • BlogIcon exedra 2008.04.16 13:37

    좋은 글 잘 읽었습니다.
    첨언하자면, XML 파서에는 크게 DOM 파서와 SAX 파서라는 두 가지 종류가 있습니다. MSXML도 이 두 가지 종류의 파서를 가지고 있죠. DOM 파서는 XML문서를 읽어서 우리가 흔히 생각하는 계층형의 트리를 만들어 메모리에 저장합니다. 그래서 파싱 후에는 그냥 해당 데이타의 path만 알면 해당 데이타를 읽을 수 있고, 수정할 수도 있습니다. 뿐만 아니라 트리 구조에 노드를 추가하듯이 XML element나 attribute를 추가할 수 있습니다. 반면 SAX 파서는 XML문서를 읽으면서 tag를 만날 때마다 적당한 event를 발생시켜줍니다. 위의 예제에 나오듯이 StartElement혹은 EndElement같은 형식의 event를 발생시켜주는 것이죠. 이 파서는 이 이외의 다른 일은 전혀 하지 않습니다. 그래서 XML문서를 파싱할 때, 사용자가 원하는 작업을 본문의 예제와 같이 직접 지정해 줘야 합니다.
    DOM 파서와 SAX 파서는 각기 장단점을 갖고 있는데요, DOM파서의 경우 XML문서가 작고, 각 데이타에 임의 접근이 빈번하게 일어날 때 유용합니다. 즉, DOM을 일종의 데이타 저장소로 사용하고, XMLWriter를 이용해서 XML 파일로 해당 내용을 백업하는 방식으로 쓸 때 유용하죠. 대신 XML 문서를 모두 읽고난 후에 해당 내용을 트리로 구성한 후에야 쓸 수 있기 때문에, XML 문서의 크기가 크거나 한번만 빨리 읽고 끝내야 하는 경우에는 별로 적당하지 않습니다.
    SAX 파서는 대신 한 번만 읽고 끝내기 때문에 XML문서의 내용을 내 프로그램의 다른 자료구조로 따로 재 구성해서 저장할 경우에 훨씬 유용합니다. 물론 XML문서를 한번만 스캔하고 끝내기 때문에 훨씬 빨리 동작하기도 하구요.
    XMLLite는 위 두 가지 파싱 방법 중에서 SAX만 지원하고 있네요.
    해당 내용이 빠진 것같아서 주제 넘게 글을 남겼습니다. 아시는 분들은 이미 다들 잘 아시겠지만, 모르시는 분들도 계실것 같아서 ...
    그럼, 좋은 하루 되세요^^
    답글

    • BlogIcon esstory 2008.04.16 14:51 신고

      저도 잘 모르는 부분이었는데 설명 듣고 깨달았습니다.
      건질 것 없는 제 글에 좋은 정보 추가해 주셔서 감사합니다 :)

  • BlogIcon 오스카 2008.04.16 14:09

    왠만한 c++ 계열 오픈소스 쪽에서는 주로 tinyxml을 많이 쓰는거 같습니다.
    답글

  • BlogIcon 이카로스 2008.07.18 10:44

    잘보고갑니다.
    msxml --> xmllite로 바꾸고 있습니다.
    덕분에 그럭저럭 완성되어 가네요^-^
    답글

    • BlogIcon esstory 2008.07.18 10:55 신고

      xmllite 쪽이 손은 더 많이 가지만, 군더더기 없이 깔끔하게 xml 을 읽고 쓸 수 있는 거 같습니다.

  • BlogIcon zextor 2010.05.14 15:27

    지금 xml 파일 예제가 utf-8로 되어 있어서 문제가 되지 않는 것 같은데 만약에 euc-kr , 즉 멀티바이트로 저장되어 있을 경우에 대해서는 어떻게 해야 할까요?
    아마도 read() 에서 MX_E_ENCODINGSIGNATURE 를 반환할 것 같은데.. 이에 대한 해법이 있을까요?
    답글

    • BlogIcon esstory 2010.05.15 15:49 신고

      라이브러리가 좋은 점이 엔코딩에 상관없이 xml 을 파싱해 내는거 아닐까요. 변환과정은 필요 없어 보이는데.. (저도 하도 오래 전에 작업해 본거라 --)

    • redred 2011.08.29 09:04

      IMultiLanguage2* 인터페이스를 SetProperty 로 지정해 주면 알아서 잘 파싱해서 반환합니다.
      정확한 이름은 좀 틀릴 수 있지만 어쨌든 요런 식으로 가능은 하더라구요

  • 음.. 2010.09.11 12:04

    저,,초보인데요~~ ReadXMLFile에 있는 GetValue()는 무슨메소드인가요??
    pReader에서 값을 가져오는것도아니구.. 식별자를 찾을수 없다는데
    어떻게해야하나요?ㅠㅠ
    답글

    • BlogIcon esstory 2010.09.12 00:21 신고

      흠.
      속성리스트에서 원하는 값을 가져오는 함수입니다.
      위 예를 보셔도, GetValue(속성, 찾는 값) 식으로 되어 있네요 ^^; msdn 도움말을 찾으셔도 나와요 ~

  • lovekwag 2014.06.25 15:02

    getvalue에 매개변수가 3인 함수가 잇는데 그건 따로 정의 하신건가요?

    CString strValue;
    이거요 ---> GetValue(&mapAttributes,_T("Title";),strValue);
    elmt.strTitle= strValue;
    답글

    • BlogIcon esstory 2014.06.26 21:18 신고

      VC ++ 를 손 놓은지가 오래 돼서 ㅠㅠ;
      별도로 정의한 기억은 없는데,
      이전 버전이라 그런건지도 모르겠습니다.