/*
* lxml.c
* parse xml data into Lua tables using expat
* Luiz Henrique de Figueiredo <lhf@tecgraf.puc-rio.br>
* 31 Jul 2018 18:43:36
* This code is hereby placed in the public domain and also under the MIT license
*/

#include <expat.h>

#include "lua.h"
#include "lauxlib.h"

#define MYNAME		"xml"
#define MYVERSION	MYNAME " library for " LUA_VERSION " / Jul 2018 / "\
			"using "

static int next(lua_State *L)
{
	int i=lua_tointeger(L,-1)+1;
	luaL_checkstack(L,2,"XML too deep");
	lua_pushinteger(L,i);
	lua_replace(L,-2);
	return i;
}

static void XMLCALL start(void *data, const char *kind, const char **attr)
{
	lua_State *L=data;
	int i=next(L);
	lua_newtable(L);
	lua_pushvalue(L,-1);
	lua_rawseti(L,-4,i);
	lua_pushstring(L,kind);
	lua_rawseti(L,-2,0);
	for (i=0; attr[i]!=NULL; i+=2) {
		lua_pushstring(L,attr[i+1]);
		lua_setfield(L,-2,attr[i]);
	}
	lua_pushinteger(L,0);
}

static void XMLCALL end(void *data, const char *kind)
{
	lua_State *L=data;
	(void)kind;
	lua_pop(L,2);
}

static void XMLCALL text(void *data, const XML_Char *s, int len)
{
	lua_State *L=data;
	int i=next(L);
	lua_pushlstring(L,s,len);
	lua_rawseti(L,-3,i);
}

static int load(lua_State *L)
{
	size_t l;
	const char *s=luaL_checklstring(L,1,&l);
	XML_Parser p=XML_ParserCreate(NULL);
	if (p==NULL) {
		lua_pushnil(L);
		lua_pushliteral(L,"cannot create XML parser");
		return 2;
	}
	XML_SetElementHandler(p,start,end);
	XML_SetCharacterDataHandler(p,text);
	XML_SetUserData(p,L);
	lua_newtable(L);
	lua_pushinteger(L,0);
	if (XML_Parse(p,s,l,1)==XML_STATUS_ERROR) {
		lua_pushnil(L);
		lua_pushstring(L,XML_ErrorString(XML_GetErrorCode(p)));
		lua_pushinteger(L,XML_GetCurrentByteIndex(p)+1);
		XML_ParserFree(p);
		return 3;
	}
	XML_ParserFree(p);
	lua_rawgeti(L,-2,1);
	return 1;
}

LUALIB_API int luaopen_xml(lua_State *L)
{
	lua_newtable(L);
	lua_pushliteral(L,"load");		/** load(s) */
	lua_pushcfunction(L,load);
	lua_settable(L,-3);
	lua_pushliteral(L,"version");		/** version */
	lua_pushliteral(L,MYVERSION);
	lua_pushstring(L,XML_ExpatVersion());
	lua_concat(L,2);
	lua_settable(L,-3);
	return 1;
}
