[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Serializing LUA tables via TinyXml
- From: "Framework Studios: Hugo" <hugo@...>
- Date: Sun, 20 Mar 2005 21:39:32 +0100
Hi,
I couldn't find any existing source-code for serializing LUA tables to and
from Xml using TinyXml, not even on Google, so here it is. ^-^
Feel free to use this in any way you want. I've written it for game
development, so it is ment for easy storing/retrieving highscores,
game-states etc. but also to take away parameters from programmers and pass
them to artists in game development (by storing Xml-s in compressed
streams).
I haven't tested it yet on very large databases, please feel free to
comment. :)
I'll cut & paste the source below this message & attach it to this mail,
hopefully it will get through the mailing list filter unharmed. ;)
Good luck!
Cheers,
Hugo
// 'data' / data_xml.cpp
//
// written for the Framework Engine ((c) 2004, 2005 by Framework Studios)
// www.framework-studios.com
//
//-------------------------------------------------------------------
// "data_xml.cpp"
// Copyright (c) 2004, 2005 by Hugo F. Habets
// All Rights Reserved
//
// Permission is hereby granted, free of charge, to any person obtaining a
copy
// of this "data_xml.cpp" and associated documentation files (the
"Software"),
// to deal in the Software without restriction, including without
limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//-------------------------------------------------------------------
//
// This 'data' code snippet connects the TinyXml library and LUA 5.0.
//
// For more information about TinyXml and Lua, please see:
//
// http://sourceforge.net/projects/tinyxml/
// http://www.lua.org/
//
// The 'data' function in LUA can write a LUA table to a Xml file
// and read a Xml-file generated with 'data' back to a LUA table.
//
// In C/C++, Xml data from any source (file, stream, etc.) when using the
// same format 'data' uses, can be read to a LUA table with
ReadTableFromXml.
//
// This code has been written with these goals:
//
// * Serialize LUA tables via Xml
// * Load LUA tables from a file stream containing Xml
// * Save the index and value types for string, boolean (value only),
// number and table (value only)
// * Possibility to combine multiple Xml sources into one table
//
// Syntax:
// -- write the table MyTable to the file MyData.Xml
// data(MyTable, "MyData.Xml")
//
// -- read the file MyData.Xml into the table MyTable
// data("MyData.Xml", MyTable)
//
// Note: to read Xml data into a table, the table must already exist:
//
// -- read MyData.Xml into a new table called MyTable
// MyTable = {}
// data("MyData.Xml", MyTable)
//
// The type of the index is kept when it is a number (MyTable[1]) or a
// string (MyTable.Name).
//
// The type of the value is kept when it is a number (MyTable.Value = 10),
// a string (MyTable.Name = "Hugo"), a boolean (MyTable.Truth = true) or
// a table (MyTable.Tree = {}). In the latter, the nested table should
contain
// actual values to be fully serialized.
//
// Other types (like userdata or function) are ignored, they may be in the
// original table but will not be serialized through 'data' nor result in an
error.
//
// Empty nested tables when saved might occur in the Xml, however will be
// ignored on loading.
//
// Table hierarchy is kept (MyTable.Tree = {} MyTable.Tree.IsThisSaved =
true).
//
// The exact order of value initialization might not be kept (which
shouldn't
// be a problem in virtually any case).
//
// This code uses the "I" and "V" attributes in Xml to identify the type of
the
// "I"ndex and "V"alue. These can be "S"tring, "N"umber, "B"oolean or
"T"able.
// Single characters are used to keep the Xml file-size relatively low.
// However it should be easy to use full names for clearity, in that case
note
// this code as it is now only checks the first character to identify the
type.
//
// Since eighter TinyXml or the Xml standard seems to have a problem when
// indexing/tagging with a number, when indexed by number the number
// will be preceeded by the letter 'n' (1.000000 becomes n1.000000).
//
// Example table (texture animation):
//
// Anim = {}
// Anim[1] = {}
// Anim[1].Texture = "data\\Anim1.png"
// Anim[1].Time = 1
// Anim[2] = {}
// Anim[2].Texture = "data\\Anim2.png"
// Anim[2].Time = 2
// Anim.Mode = "aLOOP"
// Anim.AutoStart = true
//
// data(Anim, "Anim.xml")
//
// .. will give ...
//
// <!--Framework Engine Xml database - www.framework-studios.com-->
// <fwedata>
// <n1.000000 I="N" V="T">
// <Time I="S" V="N">1.000000</Time>
// <Texture I="S" V="S">data\Anim1.png</Texture>
// </n1.000000>
// <n2.000000 I="N" V="T">
// <Time I="S" V="N">2.000000</Time>
// <Texture I="S" V="S">data\Anim2.png</Texture>
// </n2.000000>
// <Mode I="S" V="S">aLOOP</Mode>
// <AutoStart I="S" V="B">true</AutoStart>
// </fwedata>
//
// To combine multiple Xml source-files into one table, simply load new
// Xml data over an already loaded table. In case a value is indexed by the
same
// index as in a previous load, the last loaded Xml file will overwrite the
value
// automagically, erasing double entries.
//
// For reading a Xml into a LUA table from a stream, read the Xml from the
stream,
// parse it with TinyXml and use ParseTableFromXml.to parse the Xml data to
a table
// from memory.
//
// Example:
// TiXmlDocument *XmlDoc = new TiXmlDocument("MyData");
// XmlDoc->Parse((const char *)MyXmlRawData);
// TiXmlElement *Root = XmlDoc->FirstChildElement("fwedata");
// ReadTableFromXml(VM, Root, XmlDoc);
// lua_setglobal(VM, "MyTableName");
// delete XmlDoc;
// actually compiling the framework engine?
#ifdef FRAMEWORKENGINE
#include "data_xml.h"
#else
// Include tinyxml.h and LUA here...
//
// Declare:
//
// extern int data(lua_State *VM);
//
// .. and register it to the LUA virtual machine.
//
// Declare for parsing a Xml Doc from stream to a LUA table:
//
// void ParseTableFromXml(lua_State *VM, TiXmlElement *Parent,
TiXmlDocument *XmlDoc);
//
// maybe also include <string.h> etc. is required.
#endif
// how many bytes are required for a string representing a (double or float)
number...?
#define MAXBYTESINASTRINGREPRESENTINGANUMBER 32
// writes a table to an open Xml document
static void WriteTableToXml(lua_State *VM, const int Index, const char
*Name, TiXmlNode *Parent)
{
// create new Xml element node
TiXmlElement Element(Name);
// if this node is a table, store index type and set value to 'T' for table
if (lua_istable(VM, -1))
{
// save type
if (lua_isnumber(VM, -2))
{
Element.SetAttribute("I", "N");
} else
{
Element.SetAttribute("I", "S");
}
Element.SetAttribute("V", "T");
}
// start table iteration
lua_pushnil(VM);
// iterate & save all valid values & tables
while (lua_next(VM, Index) != 0)
{
// is the new node a (nested) table?
if (lua_istable(VM, -1))
{
// insert the table
if (lua_isnumber(VM, -2))
{
// index is a number (note that 'lua_tostring' converts a number to
string *also* on the lua stack!)
char buf[MAXBYTESINASTRINGREPRESENTINGANUMBER];
sprintf(buf, "n%f", lua_tonumber(VM, -2)); // extra 'n' to deceive
tinyXml
WriteTableToXml(VM, -2, buf, &Element);
} else
{
// index is a string
WriteTableToXml(VM, -2, lua_tostring(VM, -2), &Element);
}
} else
// this is not a table, maybe a valid value?
{
TiXmlElement Index(NULL); // the node's index tag, holding two attributes
for index- and value- type
TiXmlText Value(NULL); // the actual value of the node
// get index & value, types & names
switch(lua_type(VM, -2))
{
case LUA_TSTRING :
Index.SetValue(lua_tostring(VM, -2));
Index.SetAttribute("I", "S");
break;
case LUA_TNUMBER :
char buf[MAXBYTESINASTRINGREPRESENTINGANUMBER];
sprintf(buf, "n%f", lua_tonumber(VM, -2)); // extra 'n' to deceive
tinyXml
Index.SetValue(buf);
Index.SetAttribute("I", "N");
break;
default :
// to be sure, ignore other types (userdata, boolean or function) even
though one cannot index a table by these types...
lua_pop(VM, 1);
continue;
break;
}
// get & store the type of the value
if (lua_isnumber(VM, -1))
{
char buf[MAXBYTESINASTRINGREPRESENTINGANUMBER];
sprintf(buf, "%f", lua_tonumber(VM, -1));
Value.SetValue(buf);
Index.SetAttribute("V", "N");
} else if (lua_isboolean(VM, -1))
{
Value.SetValue(lua_toboolean(VM, -1) != 0 ? "true" : "false");
Index.SetAttribute("V", "B");
} else if (lua_isstring(VM, -1))
{
Value.SetValue(lua_tostring(VM, -1));
Index.SetAttribute("V", "S");
} else
{
// ignore other types
lua_pop(VM, 1);
continue;
}
Index.InsertEndChild(Value);
Element.InsertEndChild(Index);
}
lua_pop(VM, 1); // next in table iteration
}
// insert new element node to parent node
Parent->InsertEndChild(Element);
}
// parses an open & file-read Xml document into a LUA table
void ParseTableFromXml(lua_State *VM, TiXmlElement *Parent, TiXmlDocument
*XmlDoc)
{
TiXmlNode *node = Parent->IterateChildren(NULL);
while (node)
{
if (node->FirstChild() != NULL)
{
if (node->FirstChild()->Type() == TiXmlNode::NodeType::ELEMENT)
{
// this node is a tabel...
if (node->ToElement()->Attribute("I"))
{
if (node->ToElement()->Attribute("I")[0] == 'N')
{
// table is indexed by number
lua_pushnumber(VM, atof(&node->Value()[1])); // skip a byte to fool
tinyXml...
} else
{
// table is indexed by string
lua_pushstring(VM, node->Value());
}
lua_newtable(VM);
ParseTableFromXml(VM, node->ToElement(), XmlDoc);
lua_settable(VM, -3);
} else
{
// no index type
node = Parent->IterateChildren(node);
continue;
}
} else
{
// this node is value...
// is the index a string or a number?
if (node->ToElement()->Attribute("I"))
{
switch(node->ToElement()->Attribute("I")[0])
{
case 'S' :
lua_pushstring(VM, node->Value());
break;
case 'N' :
lua_pushnumber(VM, atof(&node->Value()[1])); // skip a byte to fool
tinyXml...
break;
default :
// invalid index type
node = Parent->IterateChildren(node);
continue;
break;
}
} else
{
// no index type
node = Parent->IterateChildren(node);
continue;
}
// is the value a string, boolean or a number?
if (node->ToElement()->Attribute("V"))
{
switch(node->ToElement()->Attribute("V")[0])
{
case 'S' :
lua_pushstring(VM, node->FirstChild()->Value());
lua_settable(VM, -3);
break;
case 'N' :
lua_pushnumber(VM, atof(node->FirstChild()->Value()));
lua_settable(VM, -3);
break;
case 'B' :
lua_pushboolean(VM, _strcmpi(node->FirstChild()->Value(), "true") ==
0);
lua_settable(VM, -3);
break;
default :
// if we got here, "V"alue is of invalid type but "I"ndex
// has already been pushed onto the stack, so...
lua_pop(VM, 1);
}
} else
{
// if we got here, "V"alue does not exist but "I"ndex
// has already been pushed onto the stack, so...
lua_pop(VM, 1);
}
}
}
// next node
node = Parent->IterateChildren(node);
}
}
// write example: data(t, "data.xml") -> write table 't' to file 'data.xml'
// read example: data("data.xml", t) -> read file 'data.xml' to (existing!)
table 't'
int data(lua_State *VM)
{
if ((lua_istable(VM, 1)) && (lua_gettop(VM) == 2))
{
// write...
TiXmlDocument XmlDoc(lua_tostring(VM, 2));
TiXmlComment Comment;
Comment.SetValue("Framework Engine Xml database -
www.framework-studios.com");
XmlDoc.InsertEndChild(Comment);
WriteTableToXml(VM, 1, "fwedata", &XmlDoc);
if (!XmlDoc.SaveFile())
{
lua_pushboolean(VM, false);
} else
{
lua_pushboolean(VM, true);
}
} else if (lua_istable(VM, 2)) // assumes 'false' when there is no second
parameter...
{
// read...
TiXmlDocument XmlDoc(TiXmlDocument(lua_tostring(VM, 1)));
if (!XmlDoc.LoadFile())
{
lua_pushboolean(VM, false);
} else
{
// parse the Xml data into the LUA table...
TiXmlElement *root = XmlDoc.FirstChildElement("fwedata");
if (root != NULL)
{
ParseTableFromXml(VM, root, &XmlDoc);
}
lua_pushboolean(VM, true);
}
} else
{
// actually compiling the framework engine?
#ifdef FRAMEWORKENGINE
fwse_warning("invalid call to 'data' (no table passed or invalid number of
parameters passed) - %s", SandBox->FunctionInfo());
#endif
lua_pushboolean(VM, false);
}
// return 'true' or 'false' to LUA
return 1;
}
Attachment:
data_xml.zip
Description: Zip compressed data