Lua Csv |
|
A simple parser for comma-separated-value (.csv) files:
function ParseCSVLine (line,sep) local res = {} local pos = 1 sep = sep or ',' while true do local c = string.sub(line,pos,pos) if (c == "") then break end if (c == '"') then -- quoted value (ignore separator within) local txt = "" repeat local startp,endp = string.find(line,'^%b""',pos) txt = txt..string.sub(line,startp+1,endp-1) pos = endp + 1 c = string.sub(line,pos,pos) if (c == '"') then txt = txt..'"' end -- check first char AFTER quoted string, if it is another -- quoted string without separator, then append it -- this is the way to "escape" the quote char in a quote. example: -- value1,"blub""blip""boing",value3 will result in blub"blip"boing for the middle until (c ~= '"') table.insert(res,txt) assert(c == sep or c == "") pos = pos + 1 else -- no quotes used, just look for the first separator local startp,endp = string.find(line,sep,pos) if (startp) then table.insert(res,string.sub(line,pos,startp-1)) pos = endp + 1 else -- no separator found -> use rest of string and terminate table.insert(res,string.sub(line,pos)) break end end end return res end
A simple parser for comma-separated-value (.csv) files Difference to above: * Uses metatables in case you load a csv file with column values * Code is updated
--[[ CSV Library v1 (Author: Michael Lutz, 2022-12-14) Built on: http://lua-users.org/wiki/LuaCsv csv.load = function(filename, delimiter, header) filename := CSV file to load delimiter := Delimiter (";", ",", "\t", etc..), default = ',' header := (optional) if first line is a header (true/false), default = false automatically removes quotes from text returns a table csv.save = function(filename, delimiter, data, header) filename := CSV file to write to delimiter := Delimiter (";", ",", "\t", etc..), default = ',' data := a Lua table that holds the rows and columns header := a Lua table that holds the names of the columns e.g. { "Name", "Address", "Email", ... } --]] local function parse_row(input, sep, pos) local row = {} local pos = pos or 1 --io.read() while true do local c = string.sub(input,pos,pos) if (c == "") then break end if (c == '"') then local text = '' local s,e,txt,c1 repeat s,e,txt,c1 = string.find(input, '^(.-")(.)', pos+1) text = text..txt pos = e --print(txt, e, c1) until ((c1 == sep) or (c1 == "\r") or (c1 == "\n")) --print(string.sub(text,1,-2), c1) table.insert(row, string.sub(text,1,-2)) c = c1 pos = pos + 1 else local s,e,text,c1 = string.find(input, "^([^%"..sep.."\r\n]-)([%"..sep.."\r\n])", pos) pos = e+1 --print(text, c1) table.insert(row, text) c = c1 end if c == "\n" then return row, pos end if c == "\r" then return row, pos+1 end end end csv = {} csv.load = function(filename, delimiter, header) local f,err = io.open(filename) if not f then print(err) return end local csv = f:read("*a") f:close() sep = string.sub(delimiter,1,1) or ',' local pos = 1 local t_csv = {} local f_header = nil local t_header = {} if header then t_header,pos = parse_row(csv, sep, pos) local head = {} for i,v in ipairs(t_header) do head[v] = i end f_header = function (t,k) local i = head[k] if i then return t[i] end return nil end end local row = {} row, pos = parse_row(csv, sep, pos) while row do if header then setmetatable(row, { __index = f_header }) end table.insert(t_csv, row) row, pos = parse_row(csv, sep, pos) end return t_csv, t_header end local function format_csv(str, sep) local str, matches = string.gsub(str or "", '"', '""') if (string.find(str, "[%"..sep.."\r\n]") or (matches > 0)) then return '"'..str..'"' end return str end csv.save = function(filename, delimiter, data, header) local f,err = io.open(filename, "w") if not f then print(err) return end sep = string.sub(delimiter,1,1) or ',' if header then local line = "" for i,v in ipairs(header) do line = line..format_csv(v, sep)..sep end line = string.sub(line, 1, -2) f:write(line.."\n") end for i,v in ipairs(data) do local line = "" for i2,v2 in ipairs(v) do line = line..format_csv(v2, sep)..sep end line = string.sub(line, 1, -2) f:write(line.."\n") end f:close() end return csv