XMLを用いた永続オブジェクトの試み
永続オブジェクトとXML
近頃XMLがやたらとクローズアップされてきています。サーバ/サーバ間あるいはクライアント/サーバ間のインタフェースとしてXMLを用いることで様々な接続に対応できるってことで、e-commerceをはじめとしたITの基礎技術として脚光を浴びてきたということでしょう。(e-commerceについては僕には良くわからないし、そんなbigなシステムを構築するつもりはありません^^;)
XMLで遊んでいると様々な使いみちを思い付きます。アプリケーションの設定情報なんかをXMLで書くとか、状態遷移表をXMLで表現するとか…
XMLは階層構造を持ったデータを表現する汎用のデータ・フォーマットです。階層構造を持ったデータを表現できるのなら、オブジェクトをXMLで表現できるだろうと考えました。たとえば、
class person {
string name_; // 名前
int age_; // 年齢
public:
person(string n, int a) : name_(n), age_(a) {}
...
};
のようなオブジェクトがあったとき、person(“adam”,24) を
<person> <name>adam</name> <age>24</age> </person>
のようなXMLに変換すること、逆にXMLからpersonを生成することができるんじゃないか…というわけ。
これができればアプリケーションが扱うオブジェクトの集合をXML化でき、XMLによるアプリケーション間でのオブジェクトのやりとりが可能になります。
…というわけで、XMLによるオブジェクトの永続化の試みです。
※このアーティクルでは、Microsoft XML Parser (MSXML.dll) を用いますが、XML4C や Xerces などXML Parserでも考え方は同じです。
書き込みメカニズム
例として
class person {
string name_; // 名前
int age_; // 年齢
public:
person(string n, int a) : name_(n), age_(a) {}
...
};
をXML化することを考えます。そのために新たなメソッドvirtual void save(IXMLDOMNodePtr node) constを追加します。
※IXMLDOMNodePtrはMSXMLにおけるDOMNodeのスマート・ポインタです。
メソッド save は、自分自身のメンバに基づいて DOMNode を生成します。たとえば name_ == "adam" , age_ == 24 であるpersonオブジェクトからは
<person> <name>adam</name> <age>24</age> </person>
なるDOMNodeを生成します。
次に、生成された DOMNode を引数 node の子エレメントとして追加します。たとえば node が <root/> (あるいは<root></root>) であるとすると、
<root>
<person>
<name>adam</name>
<age>24</age>
</person>
</root>
となります。node が既に子エレメントを持っていた場合、その末っ子として追加します。
まず、メソッド save の冒頭では、
- [1] “person” をタグ名とする DOMElement を作る
を行ないます。
次にメンバ変数name_から<name>adam</name> なるDOMNodeを作るには、
- [2] name_の値を文字列化し、それを値とする DOMText を作る
- [3] “age” をタグ名とする DOMElement を作る
- [4] [3]の子ノードとして[2]を追加する
というステップを踏みます。
これでふたつの DOMNode :
- [1]
<person></person> - [4]
<name>adam</name>
ができたので、
- [5] [1]の子ノードとして[4]を追加する
を行ないます。
全く同様に age_ から<age>24</age> なるDOMNode を作り、[1]の子ノードとして追加します。
最後に、save の引数として与えられた node の子ノードとして[1]を追加します。ここまでを実装してみましょう:
void person::save(IXMLDOMNodePtr node) const {
assert(node != 0);
IXMLDOMDocumentPtr doc = node->ownerDocument;
IXMLDOMNodePtr elem = doc->createElement("person");
IXMLDOMNodePtr item;
IXMLDOMTextPtr text;
item = doc->createElement("name");
text = doc->createTextNode(name_.c_str());
item->appendChild(text);
elem->appendChild(item);
item = doc->createElement("age");
char buff[16];
_itoa(age_,buff,10);
text = doc->createTextNode(buff);
item->appendChild(text);
elem->appendChild(item);
node->appendChild(elem);
}
personの派生クラス:
class programmer : public person {
string lang_; // 言語
...
};
のメソッドsave(IXMLDOMNode node) constでは、ベースクラスのメソッドsaveを再帰的に呼び出します。すなわち、
- [6] “programmer” をタグ名とする DOMElement を作る
- [7] [6]を引数にベースクラス(person)のsaveを呼び出しことで、[6]の子ノード
<person>...</person>を追加する - [8] メンバ変数 lang_ に基づいて
<language>...</language>を作る - [9] [6]の子ノードとして[8]を追加する
実装は以下のようになります:
void programmer::save(IXMLDOMNodePtr node) const {
assert(node != 0);
IXMLDOMDocumentPtr doc = node->ownerDocument;
IXMLDOMNodePtr elem = doc->createElement("programmer");
IXMLDOMNodePtr item;
IXMLDOMTextPtr text;
person::save(elem);
item = doc->createElement("language");
text = doc->createTextNode(lang_.c_str());
item->appendChild(text);
elem->appendChild(item);
node->appendChild(elem);
}
これによって、programmerからは以下のような DOMNode が生成されることになります:
<programmer>
<person>
<name>bill</name>
<age>31</age>
</person>
<language>BASIC</language>
</programmer>
最後に、XMLとしての体裁を整える、すなわち、
- [10] DOMDocument を生成する
- [11] rootエレメント を作る
- [12] [11]で作ったrootエレメントに対し、オブジェクト(person or programmer)をsaveする
という処理を行なうことでXMLによる永続データの完成です。
int save(string file) {
// 空のドキュメントを生成する
IXMLDOMDocumentPtr doc("MSXML.DOMDocument");
doc->appendChild(
doc->createProcessingInstruction(
// ルートエレメントを生成する。
IXMLDOMNodePtr root = doc->createElement("root");
doc->appendChild(root);
// [1] personとprogrammerをrootにsave
cout << "save person & programmer¥n";
person("adam",24).save(root);
programmer("bill",20,"BASIC").save(root);
// [2] vector<programmer>を生成
vector<programmer> pv;
pv.push_back(programmer("bjarne",28,"C++"));
pv.push_back(programmer("wirth",30,"Modula-2"));
// [3] rootの子"programmers"を作り、
// その中にvector<programmer>をsave
cout << "save programmers¥n";
IXMLDOMNodePtr pvelm = doc->createElement("programmers");
for ( int i = 0; i < pv.size(); ++i )
pv[i].save(pvelm);
root->appendChild(pvelm);
// XMLファイルを出力
doc->save(file.c_str());
return 0;
}
上記のサンプルコードによって生成されたpersist.xmlをIE5でブラウズしたイメージを以下に示します:

読み込みメカニズム
読み込みは前述の書き込みと同じ順序で DOMNode をほどいてゆきます。
すなわち personのメソッド void load(IXMLDOMNodePtr node)では、
- [1] 最初の子ノード(
<name>...</name>)を取得する - [2] [1]の子ノード(DOMText)を取得する
- [3] [2]から得られる文字列を基にメンバ変数 name_ をセットする
- [4] 次の子ノード(
<age>...</age>)を取得する - [5] [4]の子ノード(DOMText)を取得する
- [6] [5]から得られる文字列を基にメンバ変数 age_ をセットする
void person::load(IXMLDOMNodePtr node) {
assert(node != 0);
assert(node->nodeName == _bstr_t("person"));
IXMLDOMNodePtr item;
IXMLDOMNodePtr text;
item = node->firstChild;
text = item->firstChild;
name_ = static_cast<const char*>(_bstr_t(text->nodeValue));
item = item->nextSibling;
text = item->firstChild;
age_ = atoi(static_cast<const char*>(_bstr_t(text->nodeValue)));
}
同様に programmer::load(IXMLDOMNodePtr node) では:
- [7] 最初の子ノード(
<person>...</person>)を取得する - [8] [7]を引数にベースクラス(person)のメソッドloadを呼び出し、ベースクラスのメンバ変数をセットする
- [9] 次の子ノード(
<language>...</language>)を取得する - [10] [9]の子ノード(DOMText)を取得する
- [11] [10]から得られる文字列を基にメンバ変数 lang_ をセットする
void programmer::load(IXMLDOMNodePtr node) {
assert(node != 0);
assert(node->nodeName == _bstr_t("programmer"));
IXMLDOMNodePtr item;
IXMLDOMNodePtr text;
item = node->firstChild;
person::load(item);
item = item->nextSibling;
text = item->firstChild;
lang_ = static_cast<const char*>(_bstr_t(text->nodeValue));
}
永続データ(すなわちXML)からオブジェクトを復元するには:
- [12] XMLをパースし、DOMDocument を取得する
- [11] [12]からrootエレメントを取得する
- [12] [11]で作ったrootエレメントに対し、オブジェクト(person or programmer)をloadする
という処理を行なうことになります。
int load(string file) {
IXMLDOMDocumentPtr doc("MSXML.DOMDocument");
doc->validateOnParse = VARIANT_FALSE;
doc->load(file.c_str());
IXMLDOMNodePtr root = doc->documentElement;
IXMLDOMNodePtr node;
// save:[1] でsaveした person/programmer を読み出す
cout << "¥nload person & programmer¥n";
int i;
person* p;
node = root->firstChild;
p = node->nodeName == _bstr_t("person") ? new person : new programmer;
p->load(node);
p->print(cout);
cout << endl;
delete p;
node = node->nextSibling;
p = node->nodeName == _bstr_t("person") ? new person : new programmer;
p->load(node);
p->print(cout);
cout << endl;
delete p;
// save:[3] でsaveしたvector<programmer>を読み出す
cout << "¥nload programmers¥n";
vector<programmer> pv;
node = node->nextSibling;
for ( node = node->firstChild; node != 0; node = node->nextSibling ) {
programmer p;
p.load(node);
pv.push_back(p);
}
for ( i = 0; i < pv.size(); ++i ) {
pv[i].print(cout);
cout << endl;
}
// saveされたpersonを出力
// このとき、programmerが内包するpersonも含む
cout << "¥nload persons (including programmers)¥n";
IXMLDOMNodeListPtr nodes = doc->getElementsByTagName("person");
for ( i = 0; i < nodes->length; ++i ) {
person p;
p.load(nodes->item[i]);
p.print(cout);
cout << endl;
}
return 0;
}
上記コードの実行結果は以下のようになります:

サンプルコード
#include <iostream> // ostream
#include <string> // string
#include <vector> // vector
#include <cassert> // assert
#include <cstdlib> // atoi, _itoa
#import "msxml.dll" rename_namespace("msxml")
namespace msxml {
/*
* テストのための2つのクラス : person と programmer
*/
class person {
std::string name_;
int age_;
public:
person(std::string name, int age)
: name_(name), age_(age) {}
person() {}
virtual void save(IXMLDOMNodePtr node) const {
assert(node != 0);
IXMLDOMDocumentPtr doc = node->ownerDocument;
IXMLDOMNodePtr elem = doc->createElement("person");
IXMLDOMNodePtr item;
IXMLDOMTextPtr text;
item = doc->createElement("name");
text = doc->createTextNode(name_.c_str());
item->appendChild(text);
elem->appendChild(item);
item = doc->createElement("age");
char buff[16];
_itoa(age_,buff,10);
text = doc->createTextNode(buff);
item->appendChild(text);
elem->appendChild(item);
node->appendChild(elem);
}
virtual void load(IXMLDOMNodePtr node) {
assert(node != 0);
assert(node->nodeName == _bstr_t("person"));
IXMLDOMNodePtr item;
IXMLDOMNodePtr text;
item = node->firstChild;
text = item->firstChild;
name_ = static_cast<const char*>(_bstr_t(text->nodeValue));
item = item->nextSibling;
text = item->firstChild;
age_ = atoi(static_cast<const char*>(_bstr_t(text->nodeValue)));
}
virtual void print(std::ostream& strm) const {
strm << name_ << '(' << age_ << ')';
}
};
class programmer : public person {
std::string lang_;
public:
programmer(std::string name, int age, std::string lang)
: person(name,age), lang_(lang) {}
programmer() {}
virtual void save(IXMLDOMNodePtr node) const {
assert(node != 0);
IXMLDOMDocumentPtr doc = node->ownerDocument;
IXMLDOMNodePtr elem = doc->createElement("programmer");
IXMLDOMNodePtr item;
IXMLDOMTextPtr text;
person::save(elem);
item = doc->createElement("language");
text = doc->createTextNode(lang_.c_str());
item->appendChild(text);
elem->appendChild(item);
node->appendChild(elem);
}
virtual void load(IXMLDOMNodePtr node) {
assert(node != 0);
assert(node->nodeName == _bstr_t("programmer"));
IXMLDOMNodePtr item;
IXMLDOMNodePtr text;
item = node->firstChild;
person::load(item);
item = item->nextSibling;
text = item->firstChild;
lang_ = static_cast<const char*>(_bstr_t(text->nodeValue));
}
virtual void print(std::ostream& strm) const {
person::print(strm);
strm << ':' << lang_;
}
};
/*
* お試し
*/
using namespace std;
int save(string file) {
// 空のドキュメントを生成する
IXMLDOMDocumentPtr doc("MSXML.DOMDocument");
doc->appendChild(
doc->createProcessingInstruction(
// ルートエレメントを生成する。
IXMLDOMNodePtr root = doc->createElement("root");
doc->appendChild(root);
// [1] personとprogrammerをrootにsave
cout << "save person & programmer¥n";
person("adam",24).save(root);
programmer("bill",20,"BASIC").save(root);
// [2] vector<programmer>を生成
vector<programmer> pv;
pv.push_back(programmer("bjarne",28,"C++"));
pv.push_back(programmer("wirth",30,"Modula-2"));
// [3] rootの子"programmers"を作り、
// その中にvector<programmer>をsave
cout << "save programmers¥n";
IXMLDOMNodePtr pvelm = doc->createElement("programmers");
for ( int i = 0; i < pv.size(); ++i )
pv[i].save(pvelm);
root->appendChild(pvelm);
// XMLファイルを出力
doc->save(file.c_str());
return 0;
}
int load(string file) {
IXMLDOMDocumentPtr doc("MSXML.DOMDocument");
doc->validateOnParse = VARIANT_FALSE;
doc->load(file.c_str());
IXMLDOMNodePtr root = doc->documentElement;
IXMLDOMNodePtr node;
// [1] でsaveした person/programmer を読み出す
cout << "¥nload person & programmer¥n";
int i;
person* p;
node = root->firstChild;
p = node->nodeName == _bstr_t("person") ? new person : new programmer;
p->load(node);
p->print(cout);
cout << endl;
delete p;
node = node->nextSibling;
p = node->nodeName == _bstr_t("person") ? new person : new programmer;
p->load(node);
p->print(cout);
cout << endl;
delete p;
// [3] でsaveしたvector<programmer>を読み出す
cout << "¥nload programmers¥n";
vector<programmer> pv;
node = node->nextSibling;
for ( node = node->firstChild; node != 0; node = node->nextSibling ) {
programmer p;
p.load(node);
pv.push_back(p);
}
for ( i = 0; i < pv.size(); ++i ) {
pv[i].print(cout);
cout << endl;
}
// saveされたpersonを出力
// このとき、programmerが内包するpersonも含む
cout << "¥nload persons (including programmers)¥n";
IXMLDOMNodeListPtr nodes = doc->getElementsByTagName("person");
for ( i = 0; i < nodes->length; ++i ) {
person p;
p.load(nodes->item[i]);
p.print(cout);
cout << endl;
}
return 0;
}
int entry(string file) {
int result;
if ( result = save(file) ) return result;
if ( result = load(file) ) return result;
return result;
}
}
using namespace msxml;
int main(int argc, char* argv[]) {
if ( FAILED(CoInitialize(0)) ) return 1;
int result = entry("persist.xml");
CoUninitialize();
return result;
}