사용자 도구

사이트 도구


kb:luacsvparser

Lua CSV Parser

루아를 이용해서 CSV 파일을 다루기 위해 DLL을 만들었다. 루아에서 DLL 파일을 로드하는 방법은 LuaBinaryExtension 페이지를 참고하기 바란다. Visual C++ CRT 라이브러리를 정적으로 링크해 버렸더니 하는 일에 비해 크기가 꽤 커저버렸다.

사용법

불러오기

파일에서 불러올 수도 있고, 스트링을 그냥 파싱할 수도 있다.

  • 파싱 함수 반환값은 2차원 배열이다.
  • 라인 맨 앞 글자가 '#'인 경우 그 라인 전체를 주석으로 간주한다.
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차원 배열을 넘기면 된다.

  • 문자열, 숫자, 불린 값만 저장이 가능하다.
  • NIL 값은 그냥 빈 문자열로 저장이 된다.
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;
}

kb/luacsvparser.txt · 마지막으로 수정됨: 2014/11/06 16:57 (바깥 편집)