루아를 이용해서 CSV 파일을 다루기 위해 DLL을 만들었다. 루아에서 DLL 파일을 로드하는 방법은 LuaBinaryExtension 페이지를 참고하기 바란다. Visual C++ CRT 라이브러리를 정적으로 링크해 버렸더니 하는 일에 비해 크기가 꽤 커저버렸다.
파일에서 불러올 수도 있고, 스트링을 그냥 파싱할 수도 있다.
require("LuaCsv") data = csv.load_from_file("test.csv") -- 파일에서 로드 print(data[1][1]) data = csv.load_from_string("1,2\n3,4") -- 문자열에서 로드 print(data[1][1])
저장 함수에 2차원 배열을 넘기면 된다.
require("LuaCsv") t = {{1,2},{3,4}} csv.save_to_file(t, "test.csv") -- 파일로 저장 print(csv.save_to_string(t)) -- 문자열로 저장
require("LuaCsv") print(csv.seperator) print(csv.quote) print(csv.version)
CsvFileProcessing 페이지에서 가져온 소스를 적당히 손 본 것이다.
//////////////////////////////////////////////////////////////////////////////// /// \file LuaCsv.cpp /// \author excel96 /// \date 2007.3.7 //////////////////////////////////////////////////////////////////////////////// #include <windows.h> #include <fstream> #include <sstream> #include <vector> extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> const char* MODULE_NAME = "csv"; const char* MODULE_VERSION = "1.0"; const char DEFAULT_SEPERATOR = ','; const char DEFAULT_QUOTE = '"'; const char* VERSION_NAME = "version"; const char* SEPERATOR_NAME = "seperator"; const char* QUOTE_NAME = "quote"; typedef std::vector<std::string> ROW; typedef std::vector<ROW> ROWS; char get_seperator(lua_State* L) { char seperator = DEFAULT_SEPERATOR; lua_getfield(L, LUA_GLOBALSINDEX, MODULE_NAME); if (lua_type(L, -1) == LUA_TTABLE) { lua_getfield(L, -1, SEPERATOR_NAME); if (lua_type(L, -1) == LUA_TNUMBER) seperator = static_cast<char>(lua_tonumber(L, -1)); lua_pop(L, 1); } lua_pop(L, 1); return seperator; } char get_quote(lua_State* L) { char quote = DEFAULT_QUOTE; lua_getfield(L, LUA_GLOBALSINDEX, MODULE_NAME); if (lua_type(L, -1) == LUA_TTABLE) { lua_getfield(L, -1, QUOTE_NAME); if (lua_type(L, -1) == LUA_TNUMBER) quote = static_cast<char>(lua_tonumber(L, -1)); lua_pop(L, 1); } lua_pop(L, 1); return quote; } int load(lua_State* L, std::istream& stream, char seperator, char quote) { enum ParseState { STATE_NORMAL, // 일반 상태 STATE_QUOTE // 따옴표 뒤의 상태 }; if (!stream.good()) return luaL_error(L, "invalid stream"); ROWS rows; ROW* row = NULL; ParseState state = STATE_NORMAL; std::string text = ""; std::string token = ""; int lineNumber = 0; while (stream.good()) { ++lineNumber; std::getline(stream, text); if (text.empty() || (state == STATE_NORMAL && text[0] == '#')) continue; text.append(" "); const char* cur = text.c_str(); const char* end = cur + text.size(); while (cur < end) { if (state == STATE_QUOTE) { if (*cur == quote) { if (*(cur + 1) == quote) { token.append(1, quote); ++cur; } else { state = STATE_NORMAL; } } else { token.append(1, *cur); } } else if (state == STATE_NORMAL) { if (row == NULL) { rows.push_back(ROW()); row = &(rows.back()); } if (*cur == seperator) { row->push_back(token); token.clear(); } else if (*cur == quote) { state = STATE_QUOTE; } else { token.append(1, *cur); } } ++cur; } if (state == STATE_NORMAL) { if (row) { row->push_back(token.erase(token.size()-2, 2)); token.clear(); row = NULL; } } else { token.erase(token.size()-2, 2); token.append("\r\n"); } } lua_newtable(L); for (size_t j=0; j<rows.size(); ++j) { lua_pushnumber(L, static_cast<int>(j+1)); lua_newtable(L); const ROW& r = rows[j]; for (size_t i=0; i<r.size(); ++i) { lua_pushnumber(L, static_cast<int>(i+1)); lua_pushstring(L, r[i].c_str()); lua_rawset(L, -3); } lua_rawset(L, -3); } return 1; } static void save(lua_State* L, std::ostream& stream, char seperator, char quote) { luaL_checktype(L, -1, LUA_TTABLE); ROWS rows; int rowCount = 1; int colCount = 1; lua_pushnil(L); while (lua_next(L, -2) != 0) { luaL_checktype(L, -1, LUA_TTABLE); ROW row; lua_pushnil(L); while (lua_next(L, -2) != 0) { switch (lua_type(L, -1)) { case LUA_TSTRING: row.push_back(lua_tostring(L, -1)); break; case LUA_TNUMBER: row.push_back(lua_tostring(L, -1)); break; case LUA_TBOOLEAN: row.push_back(lua_toboolean(L, -1) ? "true" : "false"); break; case LUA_TNIL: row.push_back("nil"); break; default: luaL_error(L, "cannot convert (%s) column to string (row:%d,col:%d)", lua_typename(L, -1), rows.size() + 1, row.size() + 1 ); break; } lua_pop(L, 1); } lua_pop(L, 1); rows.push_back(row); } char specialChars[5] = { seperator, quote, '\r', '\n', 0 }; char quoteEscapeString[3] = { quote, quote, 0 }; for (size_t i=0; i<rows.size(); i++) { const ROW& row = rows[i]; std::string line; for (size_t j=0; j<row.size(); j++) { const std::string& token = row[j]; if (token.find_first_of(specialChars) == std::string::npos) { line.append(token); } else { line.append(1, quote); for (size_t k=0; k<token.size(); k++) { if (token[k] == quote) line.append(quoteEscapeString); else line.append(1, token[k]); } line.append(1, quote); } if (j != row.size() - 1) { line.append(1, seperator); } } stream << line << std::endl; } } static int load_from_file(lua_State* L) { const char* fileName = luaL_checkstring(L, -1); std::ifstream file(fileName, std::ios::in); if (!file) return luaL_error(L, "cannot open specified file"); char seperator = get_seperator(L); char quote = get_quote(L); return load(L, file, seperator, quote); } static int load_from_string(lua_State* L) { const char* str = luaL_checkstring(L, -1); char seperator = get_seperator(L); char quote = get_quote(L); std::stringstream memory(str); memory.imbue(std::locale::classic()); return load(L, memory, seperator, quote); } static int save_to_file(lua_State* L) { luaL_checktype(L, -1, LUA_TSTRING); luaL_checktype(L, -2, LUA_TTABLE); std::ofstream file(lua_tostring(L, -1), std::ios::out | std::ios::trunc); if (!file) return luaL_error(L, "cannot open specified file"); lua_pop(L, 1); char seperator = get_seperator(L); char quote = get_quote(L); save(L, file, seperator, quote); return 0; } static int save_to_string(lua_State* L) { luaL_checktype(L, -2, LUA_TTABLE); std::stringstream memory; memory.imbue(std::locale::classic()); char seperator = get_seperator(L); char quote = get_quote(L); save(L, memory, seperator, quote); lua_pushstring(L, memory.str().c_str()); return 1; } int __declspec(dllexport) luaopen_LuaCsv(lua_State* L) { static const luaL_Reg csvlib[] = { { "load_from_file", load_from_file }, { "load_from_string", load_from_string }, { "save_to_file", save_to_file }, { "save_to_string", save_to_string }, { NULL, NULL } }; luaL_register(L, MODULE_NAME, csvlib); lua_pushnumber(L, DEFAULT_SEPERATOR); lua_setfield(L, -2, SEPERATOR_NAME); lua_pushnumber(L, DEFAULT_QUOTE); lua_setfield(L, -2, QUOTE_NAME); lua_pushstring(L, MODULE_VERSION); lua_setfield(L, -2, VERSION_NAME); return 0; } } #ifdef _DEBUG #pragma comment(lib, "lualibDebug") #else #pragma comment(lib, "lualib") #endif BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { hModule = hModule; ul_reason_for_call = ul_reason_for_call; lpReserved = lpReserved; return TRUE; }