LuaのテーブルをXMLに保存する

TinyXMLbabelを使って 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++の)スタックオーバーフローで落ちるでしょう。


戻る