myspace / CommonLua /Core /ParseCSV.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
5.16 kB
require 'lpeg'
local Q = lpeg.P('"')
local quoted_value = Q * lpeg.Cs( ((1 - Q) + Q*Q / '"')^0 ) * Q
local raw_value = lpeg.C( (1 - lpeg.S(',\r\n"'))^0 )
local field = (lpeg.P(' ')^0 * quoted_value * lpeg.P(' ')^0 + raw_value) * lpeg.Cp()
local cr, lf = string.byte("\r\n", 1, 2)
local space = string.byte(" ", 1)
-- Loads the data from a CSV file into the 'data' table
-- 'fields_remap' maps indexes to column key names
-- 'skip_rows' causes the first (or more) rows to be skipped
---
--- Loads the data from a CSV file into the 'data' table.
---
--- @param filename string The path to the CSV file to load.
--- @param data table The table to store the loaded CSV data.
--- @param fields_remap table A mapping of column indices to column key names.
--- @param skip_rows number The number of rows to skip at the beginning of the file.
--- @return table The 'data' table containing the loaded CSV data.
---
function LoadCSV(filename, data, fields_remap, skip_rows)
local err, str = AsyncFileToString(filename)
if err or not str then
return
end
skip_rows = type(skip_rows) == "number" and skip_rows or skip_rows and 1 or 0
data = data or {}
local pos, col, row = 1, 1, {}
local value
while true do
value, pos = field:match(str, pos)
-- If there is are trailing spaces, remove all exept one
local n = #value
while n > 0 and value:byte(n) == space do
n = n - 1
end
if #value - n > 1 then
value = value:sub(1, n + 1)
end
if not fields_remap then
row[col] = value
elseif fields_remap[col] then
row[fields_remap[col]] = value
end
local ch = str:byte(pos)
if ch == lf or ch == cr or pos >= #str then
if skip_rows > 0 then
skip_rows = skip_rows - 1
else
data[#data + 1] = row
end
ch = (ch or 0) + (str:byte(pos + 1) or 0)
pos = pos + (ch == cr + lf and 2 or 1)
if pos >= #str then
break
end
col = 1
row = {}
else
col = col + 1
pos = pos + 1
end
end
return data
end
-- Saves the table data in a CSV file
-- if the row tables data[1], etc. have non-numeric indices,
-- 'fields_remap' specifies the field name for each index
---
--- Saves the contents of a table in a CSV file.
---
--- @param filename string The path to the CSV file to save.
--- @param data table The table of data to save.
--- @param fields_remap table (optional) A table that maps the indices of the data table to the field names.
--- @param captions table (optional) A table of field names to use as the header row.
--- @param separator string (optional) The character to use as the field separator.
--- @return boolean True if the file was saved successfully, false otherwise.
function SaveCSV(filename, data, fields_remap, captions, separator)
local pstr_f = pstr("", 1024 * 1024)
if separator then
pstr_f:append('sep=', separator, '\n')
else
separator = ','
end
local function append_row_values(row, fields)
for i = 1, fields and #fields or #row do
local value = row[not fields and i or fields[i]]
if IsT(value) then
value = TDevModeGetEnglishText(value, false, true)
end
value = (not value) and "" or tostring(value)
if value:find('[,\t\r\n"]') then
value = '"' .. value:gsub('"', '""') .. '"'
end
if i > 1 then
pstr_f:append(separator)
end
pstr_f:append(value)
end
pstr_f:append('\n')
end
if captions then
append_row_values(captions)
end
for _, row in ipairs(data) do
append_row_values(row, fields_remap)
end
return AsyncStringToFile(filename, pstr_f)
end
-- Saves the table data in a TXT file
-- if the row tables data[1], etc. have non-numeric indices,
-- 'fields_incl' specifies the which fields to be included
---
--- Saves the contents of a table in a TXT file.
---
--- @param filename string The path to the TXT file to save.
--- @param data table The table of data to save.
--- @param fields_incl table (optional) A table of field indices to include in the output.
--- @param captions table (optional) A table of field names to use as the header row.
--- @return boolean True if the file was saved successfully, false otherwise.
function SaveIDDiffFile(filename, data, fields_incl, captions)
local f = io.open(filename, "w+")
for i = (captions and 0 or 1), #data do
local row = (i == 0 and captions or data[i])
local values, n = {}, fields_incl and #fields_incl
for j = 1, n do
local value = fields_incl and i ~= 0 and row[fields_incl[j]] or row[j]
value = (value == nil) and "" or tostring(value)
table.insert(values, value)
end
f:write(table.concat(values, "\t"))
f:write("\n")
end
f:close()
end