반응형

https://sites.google.com/site/boostjp/tips/xml


XML의 읽기 / 쓰기

XML의 읽기, 쓰기에는 Boost Property Tree Library를 사용한다.
Boost.PropertyTree은 트리 구조의 일반 속성 관리를위한 라이브러리 XML, JSON, INI 파일 등의 통일적인 접근 방법을 제공한다.

여기에서는 Boost.PropertyTree를 사용하여 XML 파일의 읽기와 쓰기를 소개한다.

XML 요소의로드

XML 가져 오기에는 boost :: property_tree :: read_xml () 함수를 사용한다.
이 함수를 사용하려면 <boost/property_tree/xml_parser.hpp>을 포함한다.

다음 XML 파일을 읽어 보자.

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

<root>
    <str> Hello </ str>
    <values​​>
        <value> 1 </ value>
        <value> 2 </ value>
        <value> 3 </ value>
    </ values​​>
</ root>


# include <iostream>
# include <string>
# include <boost/property_tree/ptree.hpp>
# include <boost/property_tree/xml_parser.hpp>
# include <boost/foreach.hpp>
# include <boost/lexical_cast.hpp>

int main ()
{
    using namespace boost :: property_tree;

    ptree pt;
    read_xml ( "data.xml", pt);

    if (boost :: optional <std::string> str = pt. get_optional <std::string> ( "root.str") ) {
        std :: cout << str.get () << std :: endl;
    }
    else {
        std :: cout << "root.str is nothing"<< std :: endl;
    }

    BOOST_FOREACH (const ptree :: value_type & child, pt.get_child ( "root.values") ) {
        const int value = boost :: lexical_cast <int> (child.second.data ());
        std :: cout << value << std :: endl;
    }
}

실행 결과 :
Hello
1
2
3

먼저 root / str 요소를 얻으려면 XML이로드 된 boost :: property_tree : ptree에 대해 다음과 같이 지정한다 :

boost :: optional <std::string> str = pt.get_optional <std::string> ( " root.str ")


요소 액세스 경로 지정은 XPath 대신 마침표 액세스한다.
get_optional 함수는 지정된 형식으로 변환 된 요소가 반환된다. 요소의 취득에 실패했을 경우는 boost :: optional 잘못된 값이 반환된다.



그런 다음 root / values​​ / value 요소를 열거하려면 boost :: property_tree :: ptree 대해 get_child () 함수에서 경로 지정 하위 트리를 검색한다. 취득한 자식 트리를 BOOST_FOREACH를 반복하고 문자열 (std :: string)로 만들어지는 각 value 요소를 int로 변환하고 있어요.


BOOST_FOREACH (const ptree :: value_type & child, pt. get_child ( "root.values")


속성의 취득

XML 요소의 속성을 가져 오려면 "<xmlattr>"라는 특수한 요소 이름을 경로 지정함으로써 얻을 수있다. 
다음은 속성이있는 XML을로드 예이다 :

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

<root>
    <data id="3" name="str"​​/>
</ root>

# include <iostream>
# include <string>
# include <boost/property_tree/ptree.hpp>
# include <boost/property_tree/xml_parser.hpp>

int main ()
{
    using namespace boost :: property_tree;

    ptree pt;
    read_xml ( "data.xml", pt);

    if (boost :: optional <int> id = pt.get_optional <int> ( "root.data. <xmlattr> . id ")) {
        std :: cout << id.get () << std :: endl;
    }
    else {
        std :: cout << "id is nothing"<< std :: endl;
    }

    if (boost :: optional <std::string> name =
            pt.get_optional <std::string> ( "root.data. <xmlattr>. name")) {
        std :: cout << name.get () << std :: endl;
    }
    else {
        std :: cout << "name is nothing"<< std :: endl;
    }
}

실행 결과 :
3
str


XML 작성

XML을 쓰려면 요소를 추가하기 위해 boost :: property_tree :: ptree의 add () 멤버 함수를 사용하여 put () 멤버 함수에서 값을 설정한다.
저장은 boost :: property_tree :: write_xml에 파일 이름과 ptree을 지정한다.

# include <vector>
# include <string>
# include <boost/property_tree/ptree.hpp>
# include <boost/property_tree/xml_parser.hpp>
# include <boost/foreach.hpp>

struct Book {
    std :: string title;
    std :: string author;

    Book () {}
    Book (const Book & other)
        : title (other.title), author (other.author) {}

    Book (const std :: string & title, const std :: string & author)
        : title (title), author (author) {}
};

int main ()
{
    std :: vector <Book> books;
    books.reserve (2);
    books.push_back (Book ( "D & E", "Bjarne Stroustrup"));
    books.push_back (Book ( "MC + + D", "Andrei, Alexandrescu"));

    using boost :: property_tree :: ptree;

    ptree pt;
    BOOST_FOREACH (const Book & book, books) {
        ptree & child = pt.add ( "bookList.book", "");
        child.put ( "<xmlattr>. title"book.title);
        child.put ( "<xmlattr>. author"book.author);
    }

    using namespace boost :: property_tree :: xml_parser;
    const int indent = 2;
    write_xml ( "book.xml", pt std :: locale ()
        xml_writer_make_settings ( '', indent, widen <char> ( "utf-8")));
}

book.xml
<? xml version = "1.0"encoding = "utf-8"?>
<bookList>
  <book title="D&E" author="Bjarne Stroustrup"/>
  <book title="MC++D" author="Andrei Alexandrescu"/>
</ bookList>

write_xml는 제 3 인수 이후를 생략하면 들여 쓰기와 줄 바꿈이 생략된다.
xml_writer_make_settings을 사용하여 들여 쓰기 및 인코딩을 설정할 수있다.






출처 : http://kindtis.tistory.com/358



JSON 포맷 사용을 위해 boost의 property_tree를 사용하기로 했습니다. 실제로 사용해본 결과 그 간편함이 XML과 tinyXML 조합 때 보다 훨씬 쾌적했습니다.  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- JSON 예제 파일 -->
{
    "String": "테스트",
    "Number": 12345,
    "Boolen": true,
    "StrArray": [ "테스트1", "테스트2" ],
    "NumArray": [ 1, 2, 3, 4, 5 ],
    "SubValue" :
    {
    "SubString": "서브테스트",
    "SubNumber": 67890,
    "SubBoolen": false
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// JSON 입력 테스트
EXPECT_TRUE( exists( "test.json" ) );
 
wptree pt;
read_json( "test.json", pt, locale("kor") );
 
wstring strTest = pt.get<wstring>( L"String" );
EXPECT_TRUE( 0 == strTest.compare( L"테스트" ) );
 
int numTest = pt.get( L"Number", 0 );
EXPECT_TRUE( 12345 == numTest );
 
bool boolTest = pt.get( L"Boolen", false );
EXPECT_TRUE( boolTest );
 
vector<wstring> strArrayTest;
BOOST_FOREACH( wptree::value_type &v, pt.get_child( L"StrArray" ) )
{
    strArrayTest.push_back( v.second.data() );
}
EXPECT_TRUE( 2 == (int)strArrayTest.size() );
     
vector<wstring> numArrayTest;
BOOST_FOREACH( wptree::value_type &v, pt.get_child( L"NumArray" ) )
{
    numArrayTest.push_back( v.second.data() );
}
EXPECT_TRUE( 5 == (int)numArrayTest.size() );
 
wstring strSubTest = pt.get<wstring>( L"SubValue.SubString" );
EXPECT_TRUE( 0 == strSubTest.compare( L"서브테스트" ) );
 
int numSubTest = pt.get( L"SubValue.SubNumber", 0 );
EXPECT_TRUE( 67890 == numSubTest );
 
bool boolSubTest = pt.get( L"SubValue.SubBoolen", true );
EXPECT_TRUE( false == boolSubTest );

위의 샘플 코드를 보시면 아시겠지만 굉장히 직관적입니다. 읽고, 가져다 쓰기만 하면 되죠. 타입도 대부분 알아서 정해줍니다. 다만, 배열이 조금 문제인데 이것이 무조건 문자열로만 읽는 것 같습니다. ( 해결법 아시는 분은 자비 점... )

문제는 좀더 있습니다. 특히 쓰기가 문제입니다. boost 1.46.1 버전 기준으로 property_tree를 이용해 JSON 파일 쓰기를 하면, 모든 값들이 문자열로만 저장이 됩니다. int 형이나 bool 형 상관없이 무조건 문자열로만 저장입니다. 읽기와는 전혀 딴판이죠. 그래서 구글링으로 해결책을 찾아보던 중 property_tree의 소스를 조금 수정 하는 것으로 이 문제를 해결할 수 있었습니다.

json_parser_write.hpp의 52 줄 부분을 보시면 밑의 스샷과 같은 부분이 있습니다.
1
2
3
4
5
6
7
8
// Value or object or array
if (indent > 0 && pt.empty())
{
    // Write value
    Str data = create_escapes(pt.template get_value <Str>());
    //stream << Ch('"') << data << Ch('"');
    stream << data;
}

중간에 제가 주석을 쳐놓은 부분이 있습니다. 이 부분이 모든 값을 " "로 감싸어 문자열로 만들어 버리는 부분입니다. 이 부분을 주석 처리하면 문자열이 아닌 값으로 저장이 되죠. 하지만 이렇게만 두면 문자열로 저장하고 싶은 부분도 " " 감싸짐 없이 그대로 저장되기때문에 파싱 할때 오류가 나게 됩니다.

문자열은 예외를 둬야 하죠. 다행히 propert_tree는 값을 지정할때 Translator를 지정할 수 있습니다. 이 기능을 이용해 문자열에만 " "를 감싸 줄 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T>
struct tr
{
    typedef T internal_type;
    typedef T external_type;
 
    boost::optional<T> get_value(const T &v) { return  v.substr(1, v.size() - 2); }
    boost::optional<T> put_value(const T &v) { return L'"' + v + L'"'; }
};
 
wptree wPt;
wPt.put( L"StringTest", L"Test", tr<wstring>() );
wPt.put( L"NumTest", 1234 );
wPt.put( L"BoolenTest", false );
write_json( "writeTest.json", wPt );
1
2
3
4
5
6
<!-- 출력 결과물 -->
{
    "StringTest": "Test",
    "NumTest": 1234,
    "BoolenTest": false
}
이제 문자열과 값이 구분되어 잘 나옵니다. 하지만 아직 한가지 문제가 더 남았습니다. 바로 한글!! property_tree는 기본적으로 한글 출력이 안됩니다. 읽는 거는 locale("kor") 옵션을 주고, 읽기가 가능하지만 쓰기에서는 locale("kor") 옵션을 준다고 해도 한글 출력이 안됩니다. json_parser_write.hpp 파일 내부를 보면 그 이유를 알수 있습니다.
1
2
3
4
5
6
// This assumes an ASCII superset. But so does everything in PTree.
// We escape everything outside ASCII, because this code can't
// handle high unicode characters.
if (*b == 0x20 || *b == 0x21 || (*b >= 0x23 && *b <= 0x2E) ||
    (*b >= 0x30 && *b <= 0x5B) || (*b >= 0x5D && *b <= 0xFF))
    result += *b;
보시면 문자열 범위에서 영어권 문자열표를 제외한 특수문자는 모조리 제외되어있습니다. 이 때문에 한글을 써도 제대로 출력이 안되는 것 입니다. 여기에 한글 문자열 범위를 추가해 줍니다.
( 참고 : http://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_A000~AFFF )

수정 한 후에는 wraite_json에 locale("kor") 옵션을 주고, 쓰기를 실행 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T>
struct tr
{
    typedef T internal_type;
    typedef T external_type;
 
    boost::optional<T> get_value(const T &v) { return  v.substr(1, v.size() - 2); }
    boost::optional<T> put_value(const T &v) { return L'"' + v + L'"'; }
};
 
wptree wPt;
wPt.put( L"StringTest", L"한글 테스트", tr<wstring>() );
wPt.put( L"NumTest", 1234 );
wPt.put( L"BoolenTest", false );
write_json( "writeTest.json", wPt, locale("kor") );
1
2
3
4
5
6
<!-- 출력 결과물 -->
{
    "StringTest": "한글 테스트",
    "NumTest": 1234,
    "BoolenTest": false
}
이제 한글도 잘 나오게 되었습니다.

만세~

반응형

'메타프로그래밍 > Boost::' 카테고리의 다른 글

asin , acos, atan, asinh, acosh  (0) 2018.07.20
c+11 과 boost 메모리풀 연동  (0) 2015.03.19
xml_parser.hpp, json_parser.hpp  (0) 2014.06.23
boost::intrusive_ptr  (0) 2013.05.13
boost::pool  (0) 2013.05.11

+ Recent posts