TinyXMLと babelを使って LuaのテーブルをXMLに保存し、また読み込みます。
C++の側では以下のように書きます。 api_save_variable()とapi_load_variable()はlua_register()して Lua側から呼び出せるようにします。
#include <tinyxml/tinyxml.h> #include <babel/babel.h> #include <string> #include <sstream> using namespace std; #define _MBCS extern "C"{ #include "lua.h" #include "lualib.h" #include "lauxlib.h" } static int luavar2xml(lua_State *L, int index, TiXmlElement *e) { int luatype = lua_type(L, index); if(luatype == LUA_TNIL){ e->SetAttribute("t", LUA_TNIL); } else if(luatype == LUA_TNUMBER){ e->SetAttribute("t", LUA_TNUMBER); ostringstream oss; oss << lua_tonumber(L, index); TiXmlText text(oss.str().c_str()); e->InsertEndChild(text); } else if(luatype == LUA_TBOOLEAN){ e->SetAttribute("t", LUA_TBOOLEAN); ostringstream oss; oss << lua_toboolean(L, index); TiXmlText text(oss.str().c_str()); e->InsertEndChild(text); } else if(luatype == LUA_TSTRING){ e->SetAttribute("t", LUA_TSTRING); string sjis(lua_tostring(L, index)); string utf8 = babel::sjis_to_utf8(sjis); TiXmlText text(utf8.c_str()); // text.SetCDATA(true); e->InsertEndChild(text); } else if(luatype == LUA_TTABLE){ e->SetAttribute("t", LUA_TTABLE); // テーブル巡回。 lua_pushnil(L); while (lua_next(L, index) != 0) { // `キー´ は -2、`値´ は -1 の位置にある。 // 文字列に変換するための値をスタック上でコピーする。 lua_pushvalue(L, -2); // エレメントをテーブルのキー値の名前で作成する。 string key("_"); key += lua_tostring(L, -1); TiXmlElement ne(key.c_str()); // 文字列に変換されたキー値を捨てる。 lua_pop(L, 1); // 値を再帰で辿る。 luavar2xml(L, lua_gettop(L), &ne); e->InsertEndChild(ne); // `値´を除去、`キー´ は次の繰り返しのために残す。 lua_pop(L, 1); } } return 0; } //--------------------------------------------------------------------------- // index で指定された位置のテーブルに、XML中のキーと値のペアを格納する。 static int xml2luavar(lua_State *L, int index, const TiXmlElement *e) { int vt; // 変数タイプの読み込み。 if(e->QueryIntAttribute("t", &vt) != TIXML_SUCCESS){ return -1; } const char *key = e->Value() + 1; int ki; if(sscanf(key, "%d", &ki) == 1){ lua_pushnumber(L, ki); }else{ lua_pushstring(L, key); } // nil の処理。 if(vt == LUA_TNIL){ lua_pushnil(L); lua_settable(L, index); return 0; } // 以降で処理する変数タイプについては子が無いと意味ない。 if(e->FirstChild() == NULL){ lua_pop(L, 1); return 0; } if(e->FirstChild()->Type() == TiXmlNode::TEXT){ // TinyXMLが2.5.3の時。2.6.1の場合は TiXmlNode::TINYXML_TEXT に修正 // スカラー型変数の処理。 const TiXmlText *text = e->FirstChild()->ToText(); if(vt == LUA_TNUMBER){ double d; if(sscanf(text->Value(), "%lf", &d) == 1){ lua_pushnumber(L, d); lua_settable(L, index); } else{ lua_pop(L, 1); } } else if(vt == LUA_TBOOLEAN){ int i; if(sscanf(text->Value(), "%d", &i) == 1){ lua_pushboolean(L, i); lua_settable(L, index); } else{ lua_pop(L, 1); } } else if(vt == LUA_TSTRING){ string utf8(text->Value()); string sjis = babel::utf8_to_sjis(utf8); lua_pushstring(L, sjis.c_str()); lua_settable(L, index); } else{ lua_pop(L, 1); } } else if(vt == LUA_TTABLE){ // テーブルは再帰する。 // 新しいテーブルを追加。 lua_newtable(L); int tableindex = lua_gettop(L); for(const TiXmlElement *child = e->FirstChildElement(); child != NULL; child = child->NextSiblingElement()){ xml2luavar(L, tableindex, child); } lua_settable(L, index); } else{ lua_pop(L, 1); } return 0; } //--------------------------------------------------------------------------- static int api_save_variable( lua_State *L ) { int n = lua_gettop(L); if(n != 2 || !lua_isstring(L, 1) || !lua_istable(L, 2)){ lua_settop(L, 0); return 0; } TiXmlDocument doc(lua_tostring(L, 1)); TiXmlDeclaration dec("1.0", "utf-8", "yes"); doc.InsertEndChild(dec); TiXmlElement root("LuaVariables"); luavar2xml(L, 2, &root); doc.InsertEndChild(root); if(!doc.SaveFile()) printf("failed to save XML.\n"); lua_settop(L, 0); lua_pushboolean(L, 1); return 1; } //--------------------------------------------------------------------------- static int api_load_variable( lua_State *L ) { int n = lua_gettop(L); if(n != 1 || !lua_isstring(L, 1)){ lua_settop(L, 0); return 0; } TiXmlDocument doc(lua_tostring(L, 1)); if(!doc.LoadFile(TIXML_ENCODING_UTF8)){ return 0; } lua_settop(L, 0); lua_newtable(L); for(const TiXmlElement *child = doc.FirstChildElement()->FirstChildElement(); child != NULL; child = child->NextSiblingElement()){ xml2luavar(L, 1, child); } return 1; } //--------------------------------------------------------------------------- int main(void) { lua_State *L = lua_open(); lua_register(L, "api_save_variable", api_save_variable); lua_register(L, "api_load_variable", api_load_variable); int result = lua_dofile(L, "foo.lua"); if(result != 0){ printf("failed to load lua.\n"); } lua_close(L); return 0; } |
そしてfoo.luaは以下のように書きます。 テーブルを入れ子にしてみます。
local t1 = {a=1; b=2; c="abc"} local t2 = {"xyz", true, t1} local t = {key1="thisisapen"; key2=t2} -- テーブルをXMLに書き込む。 api_save_variable("foo.xml", t) -- XMLから読み込む。 local u = api_load_variable("foo.xml") if u ~= nil then -- 読み込み成功なら別のXMLに書き込む。 api_save_variable("foo2.xml", u) end |
先のC++プログラムでfoo.luaが実行された後、foo.xmlにLuaテーブルの内容が出力され、 またfoo2.xmlに同じ内容が出力されることが分かります。
テーブルの要素は再帰的にXMLの木構造に展開しています。 elementの名前を数字で始めることはできないので、添字の頭にアンダーバーを付けたものを名前にしています。
このプログラムでは文字列(string)と数値(number)と真偽値(boolean)だけ保存対象にしています。 それから、Luaではテーブルは参照で扱うので、循環参照を作ることができますが、 循環参照のあるテーブルを扱うと多分、無限ループに陥って(C/C++の)スタックオーバーフローで落ちるでしょう。