TinyXML + babelを使ってみる。(2) -Visitorでトラバース-

前回(TinyXML + babelを使ってみる。(1) - while( c++ );)の続き。

XMLファイルからDOMツリーを構築
TiXmlDocument doc(pFilename);
bool loadOkay = doc.LoadFile();
if (loadOkay)
{
    ...
}
else
{
    //Failed to load file
}
メモリからDOMツリーを構築
TiXmlDocument doc;
const char* str = 
    "<?xml version=\"1.0\" ?>"
    "<Hello>World</Hello>"
    ;
doc.Parse( str );
bool loadOkay = !doc.Error();
if (loadOkay)
{
    ...
}
else
{
    //Failed to load file
}

以上のように、XMLファイルまたはメモリからDOMツリーの構築が可能です。

前回はルートノードから再帰的にノードの情報を出力する関数を作りましたが(コピペ)、
今回はいわゆるVisitorパターンを使ってコンソールに出力してみます。
「Visitorさん」がノードを巡りながら「要素名、テキスト」を叫びます。
Visitorパターンに関してはGoogle先生に聞いてみて下さい。

TiXmlVisitor

TinyXMLにはDOMツリーをトラバースするためのVisitorクラスが用意されています。

class TiXmlVisitor
{
public:
    virtual ~TiXmlVisitor() {}

    /// Visit a document.
    virtual bool VisitEnter( const TiXmlDocument& /*doc*/ )        { return true; }
    /// Visit a document.
    virtual bool VisitExit( const TiXmlDocument& /*doc*/ )         { return true; }

    /// Visit an element.
    virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ )	{ return true; }
    /// Visit an element.
    virtual bool VisitExit( const TiXmlElement& /*element*/ )      { return true; }

    /// Visit a declaration
    virtual bool Visit( const TiXmlDeclaration& /*declaration*/ )  { return true; }
    /// Visit a text node
    virtual bool Visit( const TiXmlText& /*text*/ )                { return true; }
    /// Visit a comment node
    virtual bool Visit( const TiXmlComment& /*comment*/ )          { return true; }
    /// Visit an unknow node
    virtual bool Visit( const TiXmlUnknown& /*unknown*/ )          { return true; }
};

DOMツリーは6種類のノードで構成されています。

  • TiXmlNode
    • TiXmlDocument
    • TiXmlElement
    • TiXmlDeclaration
    • TiXmlText
    • TiXmlComment
    • TiXmlUnknown

「要素名」と「テキスト」を出力するためには、以下の2つメソッドをオーバーライドしたVisitorクラスを作ります。

  • bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ );
  • bool Visit( const TiXmlText& /*text*/ );
class Visitor
    : public TiXmlVisitor
{
public:
    /// Visit an element.
    virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* /*firstAttribute*/ )
    {
        std::cout << element.ValueStr() << std::endl;

        return true;
    }

    /// Visit a text node
    virtual bool Visit( const TiXmlText& text )
    {
        std::cout << babel::utf8_to_sjis( text.ValueStr() ) << std::endl;
        return true;
    }
};

各ノードはVisitorを受け入れるためのAcceptメソッドを実装しているので、ルートのTiXmlDocumentからAcceptを呼び出せば、自動的に全てのノードを巡りつつ、オーバーライドしたメソッドを呼び出してくれます。

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

    TiXmlDocument doc( "test.xml" );
    if( doc.LoadFile() )
    {
        Visitor visitor;
        doc.Accept( &visitor );
    }
    return 0;
}
実行結果
test
hoge
piyo
ぴよぴよ

さらに前回と同様の結果になるよう修正してみます。

class Visitor
    : public TiXmlVisitor
{
private:
    enum{ NUM_INDENTS_PER_SPACE = 2 };

    const char * getIndent( unsigned int numIndents )
    {
        static const char * pINDENT="                                      + ";
        static const unsigned int LENGTH=strlen( pINDENT );
        unsigned int n=numIndents*NUM_INDENTS_PER_SPACE;
        if ( n > LENGTH ) n = LENGTH;

        return &pINDENT[ LENGTH-n ];
    }

    // same as getIndent but no "+" at the end
    const char * getIndentAlt( unsigned int numIndents )
    {
        static const char * pINDENT="                                        ";
        static const unsigned int LENGTH=strlen( pINDENT );
        unsigned int n=numIndents*NUM_INDENTS_PER_SPACE;
        if ( n > LENGTH ) n = LENGTH;

        return &pINDENT[ LENGTH-n ];
    }
public:
    /// Visit a document.
    virtual bool VisitEnter( const TiXmlDocument& /*doc*/ )
    {
        std::cout << "Document" << std::endl;
        indent ++;
        return true;
    }

    /// Visit a document.
    virtual bool VisitExit( const TiXmlDocument& /*doc*/ )
    {
        indent --;
        return true;
    }

    /// Visit an element.
    virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute )
    {
        std::cout << getIndent( indent );
        std::cout << "Element: [" << element.ValueStr() << "]" << std::endl;

        int count = visit_attributes( firstAttribute );
        if( count == 0 )
            std::cout << " (No attributes)";
        else
        {
            // "1 attribute"
            std::cout << getIndentAlt(indent) << count << " attribute";
            // "n attributes"
            if( count > 1 )std::cout << "s";
        }
        std::cout << std::endl;

        indent ++;
        return true;
    }

    /// Visit an element.
    virtual bool VisitExit( const TiXmlElement& /*element*/ )
    {
        indent --;
        return true;
    }

    /// Visit a declaration
    virtual bool Visit( const TiXmlDeclaration& /*declaration*/ )
    {
        std::cout << getIndent( indent );
        std::cout << "Declaration" << std::endl;
        return true;
    }

    /// Visit a text node
    virtual bool Visit( const TiXmlText& text )
    {
        std::cout << getIndent( indent );
        std::cout << "Text: [" << babel::utf8_to_sjis( text.ValueStr() ) << "]" << std::endl;
        return true;
    }

    /// Visit a comment node
    virtual bool Visit( const TiXmlComment& comment )
    {
        std::cout << getIndent( indent );
        std::cout << "Comment: [" << comment.ValueStr() << "]" << std::endl;
        return true;
    }

    /// Visit an unknow node
    virtual bool Visit( const TiXmlUnknown& unknown )
    {
        std::cout << getIndent( indent );
        std::cout << "Unknown: [" << unknown.ValueStr() << "]" << std::endl;
        return true;
    }

private:
    int visit_attributes( const TiXmlAttribute* a )
    {
        int count = 0;
        while( a )
        {
            std::cout << getIndent( indent + 1 );
            std::cout << a->Name() << ": " << babel::utf8_to_sjis( a->ValueStr() );
            int ival = 0;
            double dval = 0;
            if( a->QueryIntValue( &ival ) == TIXML_SUCCESS )
                std::cout << " int=" << ival;
            if( a->QueryDoubleValue( &dval ) == TIXML_SUCCESS )
                std::cout << " double=" << dval;
            std::cout << std::endl;

            a = a->Next();
            count ++;
        }

        return count;
    }

public:
    Visitor()
        : indent( 0 )
    {}
private:
    int indent;
};
実行結果
Document
+ Declaration
+ Element: [test]
 (No attributes)
  + Element: [hoge]
    + a: 1 int=1 double=1
    + b: 2.0 int=2 double=2
    + c: ほげほげ
    3 attributes
  + Element: [piyo]
 (No attributes)
    + Text: [ぴよぴよ]
サンプル
  • tinyxml_01.zip 直
  • VC++2008EEで動作確認。
  • tinyxmlとbabelを使っています。
  • tinyxmlではTIXML_USE_STLを指定してstd::stringを使っています。