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