Added rowTable option to ftcsv.parse()

rowTable makes ftcsv.parse() store rows in an existing table, which
allows merging of multiple CSV files.
This commit is contained in:
Chris Smith 2017-11-02 13:52:08 +00:00
parent 4a2c22fb0a
commit e5b52d0eb1
3 changed files with 42 additions and 8 deletions

View File

@ -88,6 +88,19 @@ ftcsv.parse("apple,banana,carrot", ",", {loadFromString=true, headers=false})
In the above example, the first field becomes 'a', the second field becomes 'b' and so on. In the above example, the first field becomes 'a', the second field becomes 'b' and so on.
- `rowTable`
If you want to merge multiple CSV files, you can set `rowTable` to be an existing table to which ftcsv will append new rows.
Note: headers are not compared between multiple datasets. If there is a mismatch between headers of different CSV files, then rows from each CSV file will have missing fields.
```lua
local options = {loadFromString=true, rename={["a"] = "d", ["b"] = "e", ["c"] = "f"}}
local actual = ftcsv.parse("a,b,c\r\napple,banana,carrot", ",", options)
options.rowTable = actual
ftcsv.parse("a,b,c\r\ndamson,elderberry,fig", ",", options)
```
For all tested examples, take a look in /spec/feature_spec.lua For all tested examples, take a look in /spec/feature_spec.lua

View File

@ -122,7 +122,7 @@ local function createField(inputString, quote, fieldStart, i, doubleQuoteEscape)
end end
-- main function used to parse -- main function used to parse
local function parseString(inputString, inputLength, delimiter, i, headerField, fieldsToKeep) local function parseString(outResults, inputString, inputLength, delimiter, i, headerField, fieldsToKeep)
-- keep track of my chars! -- keep track of my chars!
local currentChar, nextChar = sbyte(inputString, i), nil local currentChar, nextChar = sbyte(inputString, i), nil
@ -141,7 +141,6 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
local delimiterByte = sbyte(delimiter) local delimiterByte = sbyte(delimiter)
local assignValue local assignValue
local outResults
-- the headers haven't been set yet. -- the headers haven't been set yet.
-- aka this is the first run! -- aka this is the first run!
if headerField == nil then if headerField == nil then
@ -153,8 +152,9 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
end end
else else
-- print("this is for magic") -- print("this is for magic")
outResults = {} outResults = outResults or {}
outResults[1] = {} lineNum = #outResults + 1
outResults[lineNum] = {}
assignValue = function() assignValue = function()
if not pcall(function() if not pcall(function()
outResults[lineNum][headerField[fieldNum]] = field outResults[lineNum][headerField[fieldNum]] = field
@ -300,6 +300,7 @@ function ftcsv.parse(inputFile, delimiter, options)
local fieldsToKeep = nil local fieldsToKeep = nil
local loadFromString = false local loadFromString = false
local headerFunc local headerFunc
local rowTable = {}
if options then if options then
if options.headers ~= nil then if options.headers ~= nil then
assert(type(options.headers) == "boolean", "ftcsv only takes the boolean 'true' or 'false' for the optional parameter 'headers' (default 'true'). You passed in '" .. tostring(options.headers) .. "' of type '" .. type(options.headers) .. "'.") assert(type(options.headers) == "boolean", "ftcsv only takes the boolean 'true' or 'false' for the optional parameter 'headers' (default 'true'). You passed in '" .. tostring(options.headers) .. "' of type '" .. type(options.headers) .. "'.")
@ -330,6 +331,10 @@ function ftcsv.parse(inputFile, delimiter, options)
assert(type(options.headerFunc) == "function", "ftcsv only takes a function value for optional parameter 'headerFunc'. You passed in '" .. tostring(options.headerFunc) .. "' of type '" .. type(options.headerFunc) .. "'.") assert(type(options.headerFunc) == "function", "ftcsv only takes a function value for optional parameter 'headerFunc'. You passed in '" .. tostring(options.headerFunc) .. "' of type '" .. type(options.headerFunc) .. "'.")
headerFunc = options.headerFunc headerFunc = options.headerFunc
end end
if options.rowTable ~= nil then
assert(type(options.rowTable) == "table", "ftcsv only takes in a table for the optional parameter 'rowTable'. You passed in '" .. tostring(options.rowTable) .. "' of type '" .. type(options.rowTable) .. "'.")
rowTable = options.rowTable
end
end end
-- handle input via string or file! -- handle input via string or file!
@ -347,7 +352,7 @@ function ftcsv.parse(inputFile, delimiter, options)
end end
-- parse through the headers! -- parse through the headers!
local headerField, i = parseString(inputString, inputLength, delimiter, 1) local headerField, i = parseString(nil, inputString, inputLength, delimiter, 1)
i = i + 1 -- start at the next char i = i + 1 -- start at the next char
-- make sure a header isn't empty -- make sure a header isn't empty
@ -389,8 +394,8 @@ function ftcsv.parse(inputFile, delimiter, options)
end end
end end
local output = parseString(inputString, inputLength, delimiter, i, headerField, fieldsToKeep) rowTable = parseString(rowTable, inputString, inputLength, delimiter, i, headerField, fieldsToKeep)
return output return rowTable
end end
-- a function that delimits " to "", used by the writer -- a function that delimits " to "", used by the writer

View File

@ -21,6 +21,22 @@ describe("csv features", function()
assert.are.same(expected, actual) assert.are.same(expected, actual)
end) end)
it("should handle merging multiple datasets", function()
local expected = {}
expected[1] = {}
expected[1].a = "apple"
expected[1].b = "banana"
expected[1].c = "carrot"
expected[2] = {}
expected[2].b = "banana"
expected[2].c = "carrot"
expected[2].d = "damson"
expected[2].e = "elderberry"
local actual = ftcsv.parse("a,b,c\napple,banana,carrot", ",", {loadFromString=true})
ftcsv.parse("b,c,d,e\nbanana,carrot,damson,elderberry", ",", {loadFromString=true, rowTable=actual})
assert.are.same(expected, actual)
end)
it("should handle renaming a field", function() it("should handle renaming a field", function()
local expected = {} local expected = {}
expected[1] = {} expected[1] = {}