TinyXML + babelを使ってみる。(3) -シリアライズ-

前回(TinyXML + babelを使ってみる。(2) -Visitorでトラバース- - while( c++ );)の続き。

今回はシリアライズ、デシリアライズについて勉強します。
といっても、XMLファイルへの出力にはまだ触れていないので、デシリアライズのみ扱います。

以下のようなXMLとクラスがある時、

<?xml version="1.0" ?>
<test>
  <hoge a="1" b="2.0" c="ほげほげ" />
</test>
class Hoge
{
private:
    int a;
    float b;
    std::string c;
};

ISerializableインターフェイスを実装し、属性(Attribute)からHogeクラスのデシリアライズを行います。
シリアライズ時に直接XMLの操作をしたくないですし、XML以外のファイルからのデシリアライズも考慮して、IArchiverインターフェイスを使って間接的に属性を取得します。

class ISerializable
{
public:
    //virtual void serialize( IArchiver* ar ) = 0;
    virtual void deserialize( IArchiver* ar ) = 0;
};
class IArchiver
{
public:
    virtual bool get_int( const std::string& name, int& value )const = 0;
    virtual bool get_float( const std::string& name, float& value )const = 0;
    virtual bool get_string( const std::string& name, std::string& value )const = 0;

    virtual void set( const std::string& name, const std::string& value ) = 0;
};

class Hoge
    : public ISerializable
{
public:
    void deserialize( IArchiver* ar )
    {
        ar->get_int( "a", a );
        ar->get_float( "b", b );
        ar->get_string( "c", c );
    }

private:
    int a;
    float b;
    std::string c;
};

IArchiver実装クラス

とりあえず、属性リストをstd::mapでテーブル化しておきます。

class Archiver
    : public IArchiver
{
    typedef std::map< std::string, std::string > table_t;

public:
    bool get_int( const std::string& name, int& value )const
    {
        return get( name, value );
    }
    
    bool get_float( const std::string& name, float& value )const
    {
        return get( name, value );
    }
    
    bool get_string( const std::string& name, std::string& value )const
    {
        return get( name, value );
    }

    void set( const std::string& name, const std::string& value )
    {
        table[ name ] = value;
    }

private:
    template< typename T >
    bool get( const std::string& name, T& value )
    {
        table_t::iterator it = table.find( name );
        if( it != table.end() )
        {
            std::stringstream ss( it->second );
            ss >> value;
            return true;
        }
        return false;
    }

private:
    table_t table;
};

Hogeクラス

Archiverの属性テーブルから値を取得してメンバに設定します。

class Hoge
    : public ISerializable
{
public:
    void deserialize( IArchiver* ar )
    {
        ar->get_int( "a", a );
        ar->get_float( "b", b );
        ar->get_string( "c", c );
    }

public:
    void func()
    {
        std::cout   << "a=" << a << std::endl
                    << "b=" << b << std::endl
                    << "c=" << c << std::endl
                    ;
    }

private:
    int a;
    float b;
    std::string c;
};

シリアライズ用Visitor

hoge要素を訪れた際に属性テーブルを生成し、Hogeクラスのデシリアライズを行います。

class Visitor
    : public TiXmlVisitor
{
public:
    /// Visit an element.
    virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute )
    {
        std::string name = element.ValueStr();

        //属性テーブルの作成
        Archiver ar;
        create_attribute_table( firstAttribute, &ar );

        //Hogeのでシリアライズ
        if( name == "hoge" )
        {
            hoge->deserialize( &ar );
        }

        return true;
    }

private:
    void create_attribute_table( const TiXmlAttribute* a, IArchiver* ar )
    {
        while( a )
        {
            ar->set( a->Name(), babel::utf8_to_sjis( a->ValueStr() ) );
            a = a->Next();
        }
    }

public:
    Visitor( Hoge* hoge )
        : hoge( hoge )
    {}

private:
    Hoge* hoge;
};


使ってみる

int main()
{
    babel::init_babel();

    Hoge hoge;
    TiXmlDocument doc( XMLファイルを指定 );
    if( doc.LoadFile() )
    {
        Visitor visitor( &hoge );
        doc.Accept( &visitor );
    }

    hoge.func();

    return 0;
}

実行結果

a=1
b=2
c=ほげほげ

サンプル

テキストノードの取得

以下のようにテキストノードとメンバの文字列を対応させるには、親要素でテキストを取得してテキストノードへのVisitを行わないように、Visitメソッドでfalseを返します。

<?xml version="1.0" ?>
<test>
  <hoge a="1" b="2.0" c="ほげほげ" />
  <piyo>ぴよぴよ</piyo>
</test>
class Piyo
{
    std::string text;
};
class Visitor
    : public TiXmlVisitor
{
public:
    /// Visit an element.
    virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute )
    {
        std::string name = element.ValueStr();

        //属性テーブルの作成
        Archiver ar;
        create_attribute_table( firstAttribute, &ar );

        //各オブジェクトのデシリアライズ
        if( name == "hoge" )
        {
            hoge->deserialize( &ar );
        }
        if( name == "piyo" )
        {
            //テキストノードの取得
            get_text( element.FirstChild(), &ar );

            piyo->deserialize( &ar );
            return false;   //子ノードのVisitを行わない
        }

        return true;
    }

    void get_text( const TiXmlNode* text, IArchiver* ar )
    {
        if( text != 0 && text->Type() == TiXmlNode::TEXT )
        {
            ar->set_text( babel::utf8_to_sjis( text->ValueStr() ) );
        }
    }

    ...
};

IArchiverインターフェイス

とりあえずテキストノードの取得、設定が出来るようにメソッドを追加しておきます。

class IArchiver
{
public:
    ...

    virtual std::string get_text()const = 0;

    virtual void set_text( const std::string& text ) = 0;
};

IArchiver実装クラス

class Archiver
    : public IArchiver
{
public:
    ...

    std::string get_text()const
    {
        return text;
    }

    void set_text( const std::string& text )
    {
        this->text = text;
    }

private:
    std::string text;
};

Piyoクラス

class Piyo
    : public ISerializable
{
public:
    void deserialize( IArchiver* ar )
    {
        text = ar->get_text();
    }

public:
    void func()
    {
        std::cout   << "text=" << text << std::endl;
    }

public:
    Piyo()
        : text( "" )
    {}

private:
    std::string text;
};

使ってみる

int main()
{
    babel::init_babel();

    Hoge hoge;
    Piyo piyo;

    TiXmlDocument doc( XMLファイルを指定 );
    if( doc.LoadFile() )
    {
        Visitor visitor( &hoge, &piyo );
        doc.Accept( &visitor );
    }

    hoge.func();
    piyo.func();

    return 0;
}

実行結果

a=1
b=2
c=ほげほげ
text=ぴよぴよ

サンプル