読者です 読者をやめる 読者になる 読者になる

Boost.PropertyTreeでXML読み込み

C++

さんざん嵌ったのでメモ。
以前、id:tt_clownさんがHello, Boost.PropertyTree!というエントリを書いていましたが
PropertyMapが正式リリースされたときに以下の破壊的な変更がありました。


boost/libs/property_map/breaking_changes.txt

  • find

find() returns an assoc_iterator.
Impact: If you use find, you may have to change your code. Most importantly,
you need to compare against not_found() instead of end().
Rationale: equal_range() also returns assoc_iterators. equal_range() cannot
return normal iterators, since the conversion would not preserve
the equal range or even the range property.

find()の戻り値で、見つかったかどうかの判定をit != pt.end()ではなくit != pt.not_found()と書くようになり、
find()の戻り値の型もiteratorからassoc_iteratorに変更されています。
この辺りは、Boost 1.42.0時点ではドキュメントが古いままなので注意してください。


それと、Property Mapのtest/exampleのどちらにも、XMLの属性読み込みのサンプルがなかったので
以下のコードのget_childに指定するパスとキー名のあたりを参考にしてください。


読み込むXML:book.xml

<bookList>
  <book title="Design and Evolution of C++" author="Bjarne Stroustrup">
    <localize locale="Japanese" title="C++の設計と進化"/>
  </book>
  <book title="C++ Template Metaprogramming" author="Dave Abrahams">
    <localize locale="Japanese" title="C++テンプレートメタプログラミング"/>
  </book>
</bookList>

BookList.h

#ifndef BOOK_LIST_INCLUDE
#define BOOK_LIST_INCLUDE

#include <string>
#include <boost/unordered_map.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/property_tree/ptree_fwd.hpp>

struct LocalizeMap {
    typedef std::string locale_type;
    typedef std::string name_type;
    typedef boost::unordered_map<locale_type, name_type> type;
};

struct Book {
    std::string title;
    std::string author;
    LocalizeMap::type localize;

    Book() {}

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

namespace mulidx {
    using namespace boost::multi_index;
}

class BookList {
    typedef mulidx::multi_index_container<
        Book,
        mulidx::indexed_by<
            mulidx::ordered_unique<mulidx::member<Book, std::string, &Book::title> >,
            mulidx::ordered_unique<mulidx::member<Book, std::string, &Book::author> >
        >
    > dictionary;

    dictionary bookList_;

public:
    void Load();

    dictionary&       get()       { return bookList_; }
    const dictionary& get() const { return bookList_; }

private:
    Book ReadBook(const boost::property_tree::ptree& book);
    void ReadLocalize(const boost::property_tree::ptree& localize, Book& data);
};

#endif // BOOK_LIST_INCLUDE

BookList.cpp

#include "BookList.h"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/foreach.hpp>
#include <boost/optional.hpp>

#define foreach BOOST_FOREACH

using boost::property_tree::ptree;

namespace {
    const std::string fileName_ = "book.xml";
}

inline boost::optional<const ptree&> FindAttr(const ptree& pt)
{
    ptree::const_assoc_iterator p = pt.find("<xmlattr>");
    return p != pt.not_found() ?
        p->second : boost::optional<const ptree&>();
}

inline std::string GetAttr(const ptree& pt, const std::string& name)
{
    return pt.find(name)->second.data();
}

void BookList::Load()
{
    bookList_.clear();

    ptree root;
    read_xml(fileName_, root);

    foreach (const ptree::value_type& pt, root.get_child("bookList")) {
        const ptree book = pt.second;

        Book data = ReadBook(book);

        foreach (const ptree::value_type& localize, book) {
            ReadLocalize(localize.second, data);
        }

        bookList_.insert(data);
    }
}

Book BookList::ReadBook(const ptree& book)
{
    boost::optional<const ptree&> p = FindAttr(book);
    if (!p)
        return Book();

    const ptree& attr = p.get();
    return Book(GetAttr(attr, "title"), GetAttr(attr, "author"));
}

void BookList::ReadLocalize(const ptree& localize, Book& data)
{
    boost::optional<const ptree&> p = FindAttr(localize);
    if (!p)
        return;

    const ptree& attr = p.get();
    data.localize["locale"] = GetAttr(attr, "locale");
    data.localize["title"]  = GetAttr(attr, "title");
}

main.cpp

#include "BookList.h"
#include <iostream>
#include <boost/foreach.hpp>
#include <boost/tuple/tuple.hpp>

#define foreach BOOST_FOREACH

int main()
{
    BookList bookList;
    bookList.Load();

    foreach (const Book& book, bookList.get()) {
        std::cout <<
            book.title  << "," <<
            book.author << "," <<
            book.localize.at("locale") << "," <<
            book.localize.at("title")
        << std::endl;
    }
}
C++ Template Metaprogramming,Dave Abrahams,Japanese,C++テンプレートメタプログラミング
Design and Evolution of C++,Bjarne Stroustrup,Japanese,C++の設計と進化