Lua Csv

lua-users home
wiki

Difference (from prior major revision) (minor diff, author diff)

Added: 55a56,197


=== Parser in Lua ===

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



Here's some resources on parsing CSV files in Lua.

Parser in Lua

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

Parser in Lua

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

RecentChanges · preferences
edit · history
Last edited January 13, 2023 11:58 pm GMT (diff)