IT 이모저모

간단하고 빠른 XML 파서 : RapidXML

exien 2018. 3. 6. 17:41

다운로드  샘플 파일 (68.9 KB)

 일년에 몇 개의 작은 도구 작성의 의뢰가 마이 코미 있습니다. 이런 일도 있으리라고 문자 코드의 변환과 멀티 스레딩 등 자주 사용하는 라이브러리를 하나의 디렉토리에 정리 한 "저희 どうぐばこ"를 준비하고 있습니다. 최근 XML을 읽고 처리하는 작은 도구를 부탁했습니다. "저희 どうぐばこ"는 XML 파서의 클래식 : Apache Xerces-C가 들어 있기는 합니다만, Xerces-C는 강력한만큼 라이브러리가 크기 때문에 작은 도구에 사용하는 것이 망설여 더 가벼운 같은 XML 파서를 찾고 "RapidXML"에 겨우 도착했습니다.

목차

 RapidXML 페이지 에서 주워 온 version 1.13의 ZIP 파일을 풀어 나온 것은 헤더가 4 개, HTML 문서 및 라이센스 조항 ...... 이만큼? 정말 무정한 구성이지만 헤더를 #include하는 것만으로 사용할 것, 경량 라이브러리의 귀감입니다. 헤더의 타임 스탬프가 2009 년 5 월에서今日日컴파일러가 솔직하게 먹고 줄까 조금 불안 했습니다만, 시험 코드를 Visual C ++ 2017이するりと받아 들여 주었으므로 우선 안심.

parse의 김기홍

 RapidXML에서 XML을 구문 분석하는 것은 매우 간단하다. xml_document을 준비하고 멤버 함수 : parse ()에 XML 문자열을 먹여뿐.

list-01 simple.cpp
#include <rapidxml.hpp> 

#include <iostream> #include <string> 
 

using namespace std ; // 이후 RapidXML 네임 스페이스 : rapidxml을 rx로 단축 namespace rx = rapidxml ; 



int main () { string xml = R "(<function include ="<cstdio> "> <return type =" int "/> printf </ function>)" "\ 0" ; // '\ 0'끝을 보장 
  rx : xml_document <> doc ; 
  doc . parse < 0 > (& xml [ 0 ]); // 인수는 char * 
   
                  

  // 루트 아래 <function> 노드 및 그 특성에 대해 
  rx : xml_node <> * node = doc . first_node ( "function" ); 
  rx : xml_attribute <> * attr = node -> first_attribute ();

  // 이름과 값을 출력 
  cout << "node :" << node -> name () << "=" << node -> value () << "\ n" ; 
  cout << "attr : " << attr -> name () << "= " << attr -> value () << "\ n " ; 
  cout << endl ;              

  for ( char ch : xml ) { cout << ( ch ! = '\ 0' ? ch : '#' ); } 
  cout << endl ; }       
fig-01
fig-01

 "function"라고 이름 붙여진 노드와 노드에 포함 된 "include"속성 (attribute)의 이름과 값을 출력합니다.

 조금 이상한 행동을 보이고 있군요 노드 : xml_node의 멤버 함수 : value ()이 노드가 내포 자식 노드 중 처음 발견 된 텍스트 노드의 값 : "printf"를 반환합니다.

 또한 parse ()에 들지시킨 XML 문자열을 ( '\ 0'을 '#'로 대체) 출력 해 보면 곳곳 '\ 0'을 포함하거나 "& lt;"/ "& gt;"을 " < "/"> "로 대체하고 있습니다. RapidXML 파서에 준 문자열을 데이터 영역으로 유용 할 것입니다 (따라서 parse ()의 인수는 const char * 아니고 char *). RapidXML에 들지시킨 문자열 파서를 사용 끝날 때까지 개방하거나 재사용하거나 안돼입니다.

 라는 것은 즉, 텍스트 파일에 기록 된 XML을 구문 분석 할 때 입력되는 텍스트 파일의 내용을 통째로 문자 영역에 보관 해 두지 냐 안됩니다. 헤더 : <rapidxml_utils.hpp>는 파일의 내용을 유지 해주는 도우미 클래스 : file이 정의되어 있습니다. 이걸로 XML 파일을 파싱 루트 노드 : person 바로 아래의 자식 노드를 열거 해 보겠습니다.

list-02 parse_file.cpp
#include <rapidxml.hpp> #include <rapidxml_utils.hpp> // rapidxml :: file 
  

#include <iostream> 

using namespace std ; namespace rx = rapidxml ; 


int main () { 
  rx : xml_document <> doc ; 
  rx : file <> input ( "trial.xml" ); 
  doc . parse < 0 > ( input . data ()); 

  rx : xml_node <> * node = doc . first_node ( "person" ); for ( rx : xml_node <> * child = node -> first_node ();  
        child ! = nullptr ;  
        child = child -> next_sibling () ) { 
    cout << child -> name () << ":" << child -> value ()
          << "\ n" ; } } 
  
list-03 trial.xml
<? xml version = "1.0" encoding = "shift_jis" ?> <person gender = "female" age = "52" >
  
  후네
  <person gender = "female" age = "24" >  
    소라
    <person gender = "male" age = "4" > 대구 </ person> </ person> <person gender = "male" age = "11" > 가다랭이 </ person> <person gender = "female" age = " 9 " > 미역 </ person> <cat gender = "male " age = "? " > 타마 </ cat> </ person>  
  
    
    
    
fig-02
fig-02

 텍스트의 전후에 불필요한 공백이 붙어 남아 있지만이를 제거 할 수 있습니다. parse () 템플릿 인수로 퍼스 옵션을 지정할 수 있습니다, parse <rapidxml :: parse_trim_whitespace> (input.data ()); 그러면이 같습니다.

fig-03
fig-03

 퍼스 옵션은 1 개 정도 정의되어 있습니다 (설명서를 읽어보십시오).

 RapidXML은 문자 코드 변환 해주지 않습니다. 이 샘플에서는 먹지시킨 trial.xml가 shift-jis이므로 (Windows에서) 문제가 없지만 XML은 UTF-8로 쓰는 것이 많기 때문에 적절히 문자 코드의 변환이 필요합니다. 예시 UTF-8 파일을 먹여 보니 당연히 바케바케이었습니다.

fig-04
fig-04

 또한 퍼스시 오류를 감지하면 parse_error 예외가 throw됩니다. parse_error에서 오류 이유 : what ()과 그 위치 : where ()을 얻을 수 있습니다. where ()가 반환 문자열에서 오류로 판정 된 위치 (포인터)입니다.

list-04 parse_error.cpp
#include <rapidxml.hpp> 

#include <iostream> #include <string> 
 

using namespace std ; namespace rx = rapidxml ; 


int main () { // ↓ 여기에 "..."가 없다! string xml = R "(<function include => <return type =" int "/> printf </ function>)" "\ 0" ; 
  rx : xml_document <char> doc ; try { 
    doc . parse < 0 > (& xml [ 0 ]); } catch ( const rx : parse_error & ex ) { 
    cerr <<ex . 
  
   
   
     what () << "@ [" << ex . where <char> () << "\ n" ; } }     
  
fig-05
fig-05

traverse : 노드간에 이동

 XML은 tree 구조를 표현하는 것이므로 tree를 구성하는 절 (노드) 사이를 떠돌아 다니는 멤버 함수가 준비되어 있습니다.

  • xml_base :: parent () : 부모 노드 (xml_base는 xml_node / xml_attribute의 base-class)
  • xml_node :: first_node () : 최초의 아이 (장자) 노드
  • xml_node :: last_node () : 마지막 자식 (막내) 노드
  • xml_node :: next_sibling () : 형제 관계에있는 다음 (동생) 노드
  • xml_node :: previous_sibling () : 형제 관계에있는 전 (오빠) 노드

 따라서, 특정 노드에 대하여 그 자식 노드를 순방향 / 역방향으로 열거한다면 :

list-05
// 순 방향 
xml_node <> * node = ... for ( xml_node <> * child = node -> first_node (); 
      child ! = nullptr ; 
      child = child -> next_sibling () ) { // child 대해 뭔가 하는 } 
    
  


// 역방향 
xml_node <> * node = ... for ( xml_node <> * child = node -> last_node (); 
      child ! = nullptr ; 
      child = child -> previous_sibling () ) { // child 대해 뭔가 하는 } 
    
  

 속성 (xml_attribute)도 마찬가지로,

  • 첫 번째 특성 : xml_node :: first_attribute ()
  • 마지막 속성 : xml_node :: first_attribute ()
  • 다음 속성 : xml_attribute :: next_sibling ()
  • 이전 속성 : xml_attribute :: previous_sibling ()

 이러한 xml_node / xml_attribute을 떠돌아 다니는 멤버 함수 군은 인수에 문자열을 주어지면 그 문자열과 노드 이름 / 속성 이름이 일치하는 것으로 한정 할 수 있습니다.

list-06
#include <rapidxml.hpp> #include <rapidxml_utils.hpp> 
 

#include <iostream> 

using namespace std ; namespace rx = rapidxml ; 


// name를 이름에 가지는 노드를 재귀 적으로 열거 void walk ( rx : xml_node <> * node , const char * name ) { for ( rx : xml_node <> * child = node -> first_node ( name ) ;  
        child ! = nullptr ;  
        child = child -> next_sibling ( name ) ) { 
    cout << child -> name
   
      () << ":" << child -> value () << "\ n" ; 
    walk ( child , name ); } }     
  


int main () { 
  rx : xml_document <> doc ; 
  rx : file <> input ( "trial.xml" ); 
  doc . parse < rx : parse_trim_whitespace > ( input . data ()); 
  walk (& doc , "person" ); // xml_document는 xml_node 파생 클래스 }   
fig-06
fig-06

 노드 이름 / 속성 이름을 지정하지 않으면 앞으로 열거하면 헤더 : <rapidxml_iterators.hpp>에 정의 된 이터레이터 : node_iterator / attribute_iterator를 사용하면 조금 편해집니다.

list-07 iterator_cpp
#include <rapidxml.hpp> #include <rapidxml_utils.hpp> #include <rapidxml_iterators.hpp> 
 
 

#include <iostream> #include <algorithm> 
 

using namespace std ; namespace rx = rapidxml ; 


// 노드를 재귀 적으로 열거 template < typename Function > void walk ( rx : xml_node <> * node , Function fun ) { 
  for_each ( rx : node_iterator <char> ( node ) rx : node_iterator <char> () [&] ( rx : xml_node <> & child ) {  
              fun ( child );  
              walk (&
 
   
             child , fun ); }); } 
            


int main () { 
  rx : xml_document <> doc ; 
  rx : file <> input ( "trial.xml" ); 
  doc . parse < rx : parse_trim_whitespace > ( input . data ()); 
  walk (& doc , [] ( rx : xml_node <> & node ) { // 요소 노드이면, 이름과 값 및 속성 이름 / 속성 값을 출력하는 if ( node . type ()  
        
         
           == rx : node_element ) { 
           cout << node . name () << ":" << node . value () << "]" ; 
           for_each ( rx : attribute_iterator <char> (& node ) rx :: attribute_iterator <char> (), [] ( const rx : xml_attribute <> &attr ) {  
                      cout <<      
                     attr . name () << "=" << attr . value () << "" ; }); 
           cout << endl ; } }); }      
                    
         
       

 node_iterator / attribute_itterator을 반환 멤버 함수 begin () / end ()를 가진 도우미 클래스 또는 전역 함수 std :: begin () / std :: end ()를 정의 해두면 range-based for 사용할 수 있습니다 주세요 :

list-08 range_based_for.cpp
#include <rapidxml.hpp> #include <rapidxml_utils.hpp> #include <rapidxml_iterators.hpp> 
 
 

template < typename Ch = char > class children { typedef rapidxml :: node_iterator < Ch > iterator_type ; public : explicit children ( rapidxml :: xml_node < Ch > * node ) : node_ ( node ) {} 
  iterator_type begin () const { return iterator_type ( node_ ); }   

  

        
  iterator_type end () const { return iterator_type (); } private : 
  rapidxml :: xml_node < Ch > * node_ ; };    



template < typename Ch = char > class attributes { typedef rapidxml :: attribute_iterator < Ch > iterator_type ; public : explicit attributes ( rapidxml :: xml_node < Ch > * node ) : node_ ( node ) {} 
  iterator_type begin () const { return iterator_type ( node_ );   

  

        } 
  iterator_type end () const { return iterator_type (); } private : 
  rapidxml :: xml_node < Ch > * node_ ; };    



namespace std { template < typename Ch > inline rapidxml :: node_iterator < Ch > begin ( rapidxml :: node_iterator < Ch > iter ) { return iter ; };
     
       

  template < typename Ch > inline rapidxml :: node_iterator < Ch > end ( rapidxml :: node_iterator < Ch > iter ) { return rapidxml :: node_iterator < Ch > (); };   
       

}

namespace std { template < typename Ch > inline rapidxml :: attribute_iterator < Ch > begin ( rapidxml :: attribute_iterator < Ch > iter ) { return iter ; };
     
       

  template < typename Ch > inline rapidxml :: attribute_iterator < Ch > end ( rapidxml :: attribute_iterator < Ch > iter ) { return rapidxml :: attribute_iterator < Ch > (); };   
       

}

#include <iostream> 

using namespace std ; namespace rx = rapidxml ; 


// 노드를 재귀 적으로 열거 template < typename Function > void walk ( rx : xml_node <> * node , Function fun ) { for ( rx : xml_node <> & child : children <> ( node ) ) { 
    fun ( child ); 
    walk (& child , fun ); } }
 
  
     
  


int main () { 
  rx : xml_document <> doc ; 
  rx : file <> input ( "trial.xml" ); 
  doc . parse < rx : parse_trim_whitespace > ( input . data ()); 
  walk (& doc , [] ( rx : xml_node <> & node ) { // 요소 노드이면, 이름과 값 및 속성 이름 / 속성 값을 출력하는 if ( node . type () 
        
         
           == rx : node_element ) { 
           cout << node . name () << ":" << node . value () << "]" ; for ( const rx : xml_attribute <> & attr : rx :: attribute_iterator <char> (& node )) { 
             cout << attr . name () << "=" <<attr .      
                value () << "" ; } 
           cout << endl ; } }); }   
           
         
       
fig-07
fig-07


'IT 이모저모' 카테고리의 다른 글

ARM, 새로운 VPU·GPU 발표 - 말리 V52·G52  (0) 2018.03.07
Barrier 란? - 1  (1) 2018.03.07
C # 역방향 레시피 - 2  (0) 2018.03.06
C # 역방향 레시피 - 1  (0) 2018.03.06
수동으로 설치하는 방법  (0) 2018.03.06