File size: 5,155 Bytes
b6a38d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
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
|