Last active
May 24, 2016 13:07
-
-
Save starwing/069ec985a48b34626a8739edabfa114d to your computer and use it in GitHub Desktop.
LuaCsv - a CSV reading/writting module for Lua language.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifdef _MSC_VER | |
# define _CRT_SECURE_NO_WARNINGS | |
#endif /* _MSC_VER */ | |
#define LUA_LIB | |
#include <lua.h> | |
#include <lauxlib.h> | |
#if LUA_VERSION_NUM < 503 | |
static int lua53_rawgeti(lua_State *L, int idx, lua_Integer n) | |
{ lua_rawgeti(L,idx,n); return lua_type(L,-1); } | |
#else | |
# define lua53_rawgeti lua_rawgeti | |
#endif | |
#if LUA_VERSION_NUM < 502 | |
# define lua_rawlen lua_objlen | |
# define luaL_newlibtable(L,l) \ | |
lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) | |
# define luaL_newlib(L,l) \ | |
(luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) | |
static int lua_absindex(lua_State *L, int idx) | |
{ return idx >= 0 || idx <= LUA_REGISTRYINDEX ? idx : idx+lua_gettop(L); } | |
static void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) { | |
luaL_checkstack(L, nup, "too many upvalues"); | |
for (; l->name != NULL; l++) { | |
int i; | |
for (i = 0; i < nup; i++) | |
lua_pushvalue(L, -nup); | |
lua_pushcclosure(L, l->func, nup); | |
lua_setfield(L, -(nup + 2), l->name); | |
} | |
lua_pop(L, nup); | |
} | |
static const char *luaL_tolstring(lua_State *L, int idx, size_t *plen) { | |
if (!luaL_callmeta(L, idx, "__tostring")) { /* no metafield? */ | |
switch (lua_type(L, idx)) { | |
case LUA_TNUMBER: | |
case LUA_TSTRING: | |
lua_pushvalue(L, idx); | |
break; | |
case LUA_TBOOLEAN: | |
lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false")); | |
break; | |
case LUA_TNIL: | |
lua_pushliteral(L, "nil"); | |
break; | |
default: | |
lua_pushfstring(L, "%s: %p", luaL_typename(L, idx), | |
lua_topointer(L, idx)); | |
break; | |
} | |
} | |
return lua_tolstring(L, -1, plen); | |
} | |
#endif | |
#include <assert.h> | |
#include <stdio.h> | |
#include <string.h> | |
#define csv_isnewline(ch) ((ch) == EOF || (ch) == '\n' || (ch) == '\r') | |
typedef const char *CSV_Reader(void *ud, size_t *plen); | |
typedef struct CSV_State { | |
/* configs */ | |
int delimiter; | |
int quote; | |
/* states */ | |
int current; | |
size_t size; | |
const char *p; | |
CSV_Reader *reader; | |
void *ud; | |
} CSV_State; | |
static void csv_init(CSV_State *S, int delimiter, int quote) { | |
memset(S, 0, sizeof(*S)); | |
S->delimiter = delimiter ? delimiter : ','; | |
S->quote = quote ? quote : '\"'; | |
} | |
static int csv_next(CSV_State *S) { | |
if (S->current == EOF) return EOF; | |
if (S->size == 0 || S->p == NULL) { | |
if (S->reader == NULL) | |
return S->current = EOF; | |
S->p = S->reader(S->ud, &S->size); | |
if (S->p == NULL || S->size == 0) | |
return S->current = EOF; | |
} | |
--S->size; | |
return S->current = *S->p++; | |
} | |
static void csv_setinput(CSV_State *S, CSV_Reader *reader, void *ud) { | |
S->size = 0; | |
S->p = NULL; | |
S->reader = reader; | |
S->ud = ud; | |
S->current = csv_next(S); | |
} | |
static int csv_readline(CSV_State *S, lua_State *L, int t) { | |
int count = 0, old; | |
luaL_Buffer B; | |
if (t == 0) { lua_createtable(L, 4, 1); t = -1; } | |
t = lua_absindex(L, t); | |
while (S->current != EOF) { | |
int quote = 0; | |
luaL_buffinit(L, &B); | |
while (S->current != EOF) { | |
if (S->current == S->quote && csv_next(S) != S->quote) | |
quote = !quote; | |
if (!quote && (csv_isnewline(S->current) | |
|| S->current == S->delimiter)) break; | |
luaL_addchar(&B, S->current); | |
csv_next(S); | |
} | |
luaL_pushresult(&B); | |
lua_rawseti(L, t, ++count); | |
if (csv_isnewline(S->current)) break; | |
if (S->current == S->delimiter) csv_next(S); | |
} | |
old = S->current; | |
csv_next(S); /* skip '\n' or '\r' */ | |
if (csv_isnewline(S->current) && S->current != old) | |
csv_next(S); /* skip '\n\r' or '\r\n' */ | |
return S->current != EOF; | |
} | |
static void csv_writeline(CSV_State *S, luaL_Buffer *B, int t) { | |
lua_State *L = B->L; | |
int quote, i, len = lua_rawlen(L, t); | |
t = lua_absindex(L, t); | |
for (i = 1; i <= len; ++i) { | |
size_t pos, len; | |
const char *s; | |
lua_rawgeti(L, t, i); | |
s = luaL_tolstring(L, -1, &len); | |
lua_pop(L, 1); | |
for (quote = 0, pos = 0; pos < len; ++pos) | |
if (s[pos] == S->quote || s[pos] == S->delimiter) | |
{ quote = 1; break; } | |
if (i != 1) luaL_addchar(B, S->delimiter); | |
if (!quote) { luaL_addvalue(B); continue; } | |
luaL_addchar(B, S->quote); | |
for (pos = 0; pos < len; ++pos) { | |
if (s[pos] == S->quote) | |
luaL_addchar(B, S->quote); | |
luaL_addchar(B, s[pos]); | |
} | |
luaL_addchar(B, S->quote); | |
} | |
} | |
/* entry point */ | |
#define RESERVEDSLOT 3 | |
typedef struct LoadS { | |
const char *s; | |
size_t size; | |
} LoadS; | |
static const char *getS(void *ud, size_t *plen) { | |
LoadS *ls = (LoadS*)ud; | |
if (ls->size == 0) return NULL; | |
*plen = ls->size; | |
ls->size = 0; | |
return ls->s; | |
} | |
static const char *generic_reader(void *ud, size_t *size) { | |
lua_State *L = (lua_State*)ud; | |
luaL_checkstack(L, 2, "too many nested functions"); | |
lua_pushvalue(L, 1); /* get function */ | |
lua_call(L, 0, 1); /* call it */ | |
if (lua_isnil(L, -1)) { | |
lua_pop(L, 1); /* pop result */ | |
*size = 0; | |
return NULL; | |
} | |
else if (!lua_isstring(L, -1)) | |
luaL_error(L, "reader function must return a string"); | |
lua_replace(L, RESERVEDSLOT); /* save string in reserved slot */ | |
return lua_tolstring(L, RESERVEDSLOT, size); | |
} | |
static int load_aux(CSV_State *S, lua_State *L) { | |
int count = 0; | |
lua_createtable(L, 4, 1); | |
while (csv_readline(S, L, 0)) | |
lua_rawseti(L, -2, ++count); | |
lua_rawseti(L, -2, ++count); | |
return 1; | |
} | |
static int Lload(lua_State *L) { | |
CSV_State S; | |
LoadS ls; | |
const char *opt = luaL_optstring(L, 2, ",\""); | |
ls.s = lua_tolstring(L, 1, &ls.size); | |
csv_init(&S, opt[0], opt[0] ? opt[1] : 0); | |
if (ls.s != NULL) | |
csv_setinput(&S, getS, &ls); | |
else { /* loading from a reader function */ | |
luaL_checktype(L, 1, LUA_TFUNCTION); | |
lua_settop(L, RESERVEDSLOT); /* create reserved slot */ | |
csv_setinput(&S, generic_reader, L); | |
} | |
return load_aux(&S, L); | |
} | |
typedef struct LoadF { | |
int n; | |
FILE *f; | |
char buff[BUFSIZ]; | |
} LoadF; | |
static const char *getF(void *ud, size_t *size) { | |
LoadF *lf = (LoadF *)ud; | |
if (lf->n > 0) { *size = lf->n; lf->n = 0; } | |
else { | |
if (feof(lf->f)) return NULL; | |
*size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); | |
} | |
return lf->buff; | |
} | |
static int errfile(lua_State *L, const char *fname, const char *what) { | |
const char *serr = strerror(errno); | |
lua_pushnil(L); | |
lua_pushfstring(L, "cannot %s %s: %s", what, fname, serr); | |
return 2; | |
} | |
static void skipBOM(LoadF *lf) { | |
const char *p = "\xEF\xBB\xBF"; | |
int c; | |
lf->n = 0; | |
do { | |
c = getc(lf->f); | |
lf->buff[lf->n++] = c; | |
if (c == EOF || c != *(const unsigned char *)p++) | |
return; | |
} while (*p != '\0'); | |
lf->n = 0; | |
} | |
static int Lloadfile(lua_State *L) { | |
CSV_State S; | |
LoadF lf; | |
int ret, readstatus; | |
const char *fname = luaL_optstring(L, 1, NULL); | |
const char *opt = luaL_optstring(L, 2, ",\""); | |
lf.f = fname ? fopen(fname, "r") : stdin; | |
if (lf.f == NULL) return errfile(L, fname, "open"); | |
skipBOM(&lf); | |
csv_init(&S, opt[0], opt[0] ? opt[1] : 0); | |
csv_setinput(&S, getF, &lf); | |
ret = load_aux(&S, L); | |
readstatus = ferror(lf.f); | |
if (fname) fclose(lf.f); /* close file (even in case of errors) */ | |
if (readstatus) { | |
errfile(L, fname ? fname : "=stdin", "read"); | |
lua_remove(L, -2); | |
return ret + 1; | |
} | |
return ret; | |
} | |
static lua_Integer posrelat(lua_Integer pos, size_t len) { | |
if (pos >= 0) return pos; | |
else if (0u - (size_t)pos > len) return 0; | |
else return (lua_Integer)len + pos + 1; | |
} | |
static int Lloadline(lua_State *L) { | |
CSV_State S; | |
size_t len; | |
const char *s = luaL_checklstring(L, 1, &len); | |
lua_Integer i = posrelat(luaL_optinteger(L, 2, 1), len); | |
const char *opt = luaL_optstring(L, 3, ",\""); | |
if (i < 1) i = 1; | |
if (i > len) return 0; | |
csv_init(&S, opt[0], opt[0] ? opt[1] : 0); | |
S.current = s[i-1]; | |
S.p = s + i; | |
S.size = (size_t)(len - i); | |
csv_readline(&S, L, 0); | |
lua_pushinteger(L, len - (S.size > 0 ? S.size : -1)); | |
return 2; | |
} | |
static int Ldump(lua_State *L) { | |
CSV_State S; | |
luaL_Buffer B; | |
int i, len; | |
const char *opt = luaL_optstring(L, 2, ",\""); | |
csv_init(&S, opt[0], opt[0] ? opt[1] : 0); | |
luaL_checktype(L, 1, LUA_TTABLE); | |
lua_settop(L, 2); | |
luaL_buffinit(L, &B); | |
for (i = 1, len = lua_rawlen(L, 1); i <= len; ++i) { | |
if (lua53_rawgeti(L, 1, i) == LUA_TTABLE) { | |
lua_replace(L, 2); | |
csv_writeline(&S, &B, 2); | |
luaL_addchar(&B, '\n'); | |
} | |
} | |
luaL_pushresult(&B); | |
return 1; | |
} | |
static int Ldumpline(lua_State *L) { | |
luaL_Buffer B; | |
CSV_State S; | |
const char *opt = luaL_optstring(L, 2, ",\""); | |
csv_init(&S, opt[0], opt[0] ? opt[1] : 0); | |
luaL_checktype(L, 1, LUA_TTABLE); | |
luaL_buffinit(L, &B); | |
csv_writeline(&S, &B, 1); | |
luaL_pushresult(&B); | |
return 1; | |
} | |
LUALIB_API int luaopen_csv(lua_State *L) { | |
luaL_Reg libs[] = { | |
#define ENTRY(name) { #name, L##name } | |
ENTRY(load), | |
ENTRY(loadline), | |
ENTRY(loadfile), | |
ENTRY(dump), | |
ENTRY(dumpline), | |
#undef ENTRY | |
{ NULL, NULL } | |
}; | |
luaL_newlib(L, libs); | |
return 1; | |
} | |
/* cc: flags+='-s -O3 -mdll -DLUA_BUILD_AS_DLL' | |
* cc: libs+='-llua53' output='csv.dll' */ | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local csv = require "csv" | |
function test1() | |
local test = [[ | |
"a","b" | |
"a,b","c,d" | |
"a","b,c" | |
"a,b","c" | |
"a",b,"c" | |
"a,b",c,"d,e" | |
"a",b,"c,d" | |
"a,b",c,"d" | |
"a","b", | |
"a,b","c,d", | |
"a","b,c", | |
"a,b","c", | |
"a",b,"c", | |
"a,b",c,"d,e", | |
"a",b,"c,d", | |
"a,b",c,"d", | |
,"a","b" | |
,"a,b","c,d" | |
,"a","b,c" | |
,"a,b","c" | |
,"a",b,"c" | |
,"a,b",c,"d,e" | |
,"a",b,"c,d" | |
,"a,b",c,"d" | |
"a,b | |
,c,d",e,f,g | |
]] | |
local result = { | |
"{a}-{b}", | |
"{a,b}-{c,d}", | |
"{a}-{b,c}", | |
"{a,b}-{c}", | |
"{a}-{b}-{c}", | |
"{a,b}-{c}-{d,e}", | |
"{a}-{b}-{c,d}", | |
"{a,b}-{c}-{d}", | |
"{a}-{b}-{}", | |
"{a,b}-{c,d}-{}", | |
"{a}-{b,c}-{}", | |
"{a,b}-{c}-{}", | |
"{a}-{b}-{c}-{}", | |
"{a,b}-{c}-{d,e}-{}", | |
"{a}-{b}-{c,d}-{}", | |
"{a,b}-{c}-{d}-{}", | |
"{}-{a}-{b}", | |
"{}-{a,b}-{c,d}", | |
"{}-{a}-{b,c}", | |
"{}-{a,b}-{c}", | |
"{}-{a}-{b}-{c}", | |
"{}-{a,b}-{c}-{d,e}", | |
"{}-{a}-{b}-{c,d}", | |
"{}-{a,b}-{c}-{d}", | |
"{a,b\n,c,d}-{e}-{f}-{g}", | |
} | |
local out = csv.load(test) | |
for i, v in ipairs(out) do | |
local r = "{"..table.concat(v, "}-{").."}" | |
assert(r == result[i]) | |
end | |
local once | |
local out = csv.load(function() | |
if not once then | |
once = true | |
return test end | |
end) | |
for i, v in ipairs(out) do | |
local r = "{"..table.concat(v, "}-{").."}" | |
assert(r == result[i]) | |
end | |
local i, pos = 1, 1 | |
while true do | |
local v, nl = csv.loadline(test, pos) | |
if not v then break end | |
local r = "{"..table.concat(v, "}-{").."}" | |
assert(r == result[i]) | |
i = i + 1 | |
pos = nl | |
end | |
local fh = assert(io.open("out.csv", "w")) | |
fh:write("\xEF\xBB\xBF", test) | |
fh:close() | |
local out = csv.loadfile "out.csv" | |
for i, v in ipairs(out) do | |
local r = "{"..table.concat(v, "}-{").."}" | |
assert(r == result[i]) | |
end | |
end | |
function test2() | |
local test = [[ | |
"Name ""x"" is that ",Product,Date,Age | |
steve,"bonzo ""dog"" catcher",10/10/09,3 | |
bonzo,dog,23/04/08,10 | |
john,CowCatcher,20/10/09,4 | |
]] | |
local result = { | |
"{Name \"x\" is that }-{Product}-{Date}-{Age}", | |
"{steve}-{bonzo \"dog\" catcher}-{10/10/09}-{3}", | |
"{bonzo}-{dog}-{23/04/08}-{10}", | |
"{john}-{CowCatcher}-{20/10/09}-{4}", | |
} | |
local out = csv.load(test) | |
for i, v in ipairs(out) do | |
local r = "{"..table.concat(v, "}-{").."}" | |
assert(r == result[i]) | |
end | |
for i, v in ipairs(out) do | |
assert(csv.dumpline(v) == | |
csv.dumpline((csv.loadline(csv.dumpline(v))))) | |
end | |
local outs = csv.dump(out) | |
assert(outs == test) | |
end | |
test1() | |
test2() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment