TinyXML-2を使う

C++XMLをぐにぐにする必要が出てきたので、良い塩梅のXMLパーサを探す事にしました。
Boost.PropertyTreeのxml_parserを現行で使っていたのですが、XMLを構築する際にCDATAセクションに対応していなかったりするので、要件に合わない場合があります。
C++XMLパーサも一長一短でどこか不自由な箇所があるので、丁度いい具合のパーサって中々無いもんなんですね。


今回は特にパフォーマンスを考慮しないとして、TinyXML-2が候補として上がりました。
名前の通りヘッダ1個とソース1個で構成されるDOM方式のとても小さいXMLパーサです。
小さいながらも一通りの機能が揃っており(もちろんCDATAセクションもフラグで管理できる)、ライセンスもzlibなので安心です。*1
ただし、文字コードに関しては対応していないのでそこは自前で変換する必要が出てきます(道化師さんのbabelやmlang.dllを使えばいいと思います)


さて、ここではTinyXML-2の読み書きについて簡単なチュートリアル的なものを書き残しておきます。

導入

githubからcloneなりzipで落とすなりしてもってきましょう。
その中のtinyxml2.hとtinyxml2.cppを使いたいプロジェクトの中に直接放り込めば完了です。
面倒くさいリンク設定などは一切ありません。

チュートリアル

まず初めに既存のXMLを読み込む事から始めるのではなく、簡単な初期化方法からいきましょう。
ここで紹介する XMLDocument::NewDeclaration()は新たにXMLを一から構築する際に忘れがちです。
XMLDocument::NewDeclaration()を行うと、Prologueが構築されメモリプールが初期化されます。
既存のXMLをロードしてそれを操作する場合は必要ありません。

#include <iostream>
#include "tinyxml2.h"

int main()
{
	using namespace tinyxml2;

	XMLDocument xml;
	XMLDeclaration* decl = xml.NewDeclaration();

	xml.InsertEndChild(decl);

	xml.Print();
	std::cout << "\n";

	// standalone等が必要になってPrologueを変更したい場合は自前で書くことも可能
	decl->SetValue("xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"");

	xml.Print();
	std::cout << "\n";

	return 0;
}
XMLを構築する(値を追加する)

TinyXML-2において大抵の要素はXMLNodeオブジェクトを継承して作られたものです。
XMLを構築する際はそれらを使って入れ子状にしていきましょう。

// XMLElementが基本で、要素を作成出来ます
XMLElement* root = xml.NewElement("root");
// まずはrootを追加します(FirstにするとPrologueの上にきちゃいます)
xml.InsertEndChild(root);

// 次は追加したroot要素に対してテキストデータを入れたいのでXMLTextを作ります
XMLText* text = xml.NewText("Text");
// root要素に対して末尾にテキストを追加します
root->InsertEndChild(text);
// テキストなのでCDATAで囲みたいので、フラグをtrueにします
text->SetCData(true);

これを出力すると、

<?xml version="1.0" encoding="UTF-8"?>
<root><![CDATA[Text]]></root>

こうなります。
ちなみに、XMLElementやXMLTextのようにノードから派生するものを生成する時はXMLDocumentから生成する必要があります。
この例の場合は直接XMLDocumentを呼んでいますが、ノードにはGetDocument()メソッドが実装されているので、それを用いて所属するXMLDocumentオブジェクトを取得する事が出来ます。

XMLText text = root->GetDocument()->NewText();

こんなかんじですね。

XMLを構築する(属性を入れてみる)

追加したroot要素にAttributeを付けてみましょう

root->SetAttribute("name", "melponn");

これだけです。

<?xml version="1.0" encoding="UTF-8"?>
<root name="melponn"><![CDATA[Text]]></root>
XMLを構築する(連続した値)
<?xml version="1.0" encoding="UTF-8"?>
<root>
	<array>
		<value>hoge</value>
		<value>hoge</value>
	</array>
</root>

続いてこのようなデータを作ってみます。

XMLElement* array = root->GetDocument()->NewElement("array");
root->InsertEndChild(array);

for ( int i=0; i<2; ++i) {
	// valueを作ってarrayへ押し込めます
	XMLElement* value = xml.NewElement("value");
	array->InsertEndChild(value);

	// valueに対して値を追加します
	value->InsertEndChild(
		value->GetDocument()->NewText("hoge")
	);
}

簡単ですね。ちなみに値は全てテキストとして扱うので、文字列以外を入れたい場合はBoost.Lexical_CastやBoost.Formatとか使うと良いと思います。

XMLを読み込む(XMLのロード)

次は既存のXMLから読み込んでみます。先ほどのXMLをtest.xmlとして使用します。

<?xml version="1.0" encoding="UTF-8"?>
<root>
	<array>
		<value>hoge</value>
		<value>hoge</value>
	</array>
</root>

ファイルからの読み込みは超簡単です。XMLDocumentにLoadFileが用意されているので、それを使用します。

#include <iostream>
#include "tinyxml2.h"

int main()
{
	using namespace tinyxml2;

	XMLDocument		xml;
	xml.LoadFile("./test.xml");

	xml.Print();
	std::cout << "\n";

	return 0;
}

ファイルパスまたはANSI CスタイルのFILEポインタを渡す事が出来、ここでは相対パスを指定しています。

XMLを読み込む(各種要素を取り出す)

前項の出力結果通り、読み込むとその時点でXMLのオブジェクトは構築されています。
それらを取り出す際は、FirstChildElement LastChildElement等を使用します。

XMLElement* root = xml.FirstChildElement("root");
XMLElement* array = root->FirstChildElement("array");
XMLElement* value = array->FirstChildElement("value");

std::cout << value->GetText() << "\n";

要素が取得できなかった場合はNULLが返ってきます。

XMLを読み込む(連続した要素を取り出す)

配列となっている要素に対しては以下のように行うとうまく取得できます

for(XMLElement* element= pt->FirstChildElement("element"); element!= NULL; element= element->NextSiblingElement("element") ) {
	...
}

NextSiblingElement()を使用することにより、次の要素を取得する事が出来ます。
続く要素がない場合、NULLを返すのでそれを判定してやればループで処理出来ますね。

とりあえずここまで。

*1:ただし、無印のTinyXMLはGPLなので注意