lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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