Added rowFunc as optional parameter to ftcsv.parse

rowFunc will be called after parsing each data row as follows:
  output[line] = rowFunc(output[line], line)

if output[line] is nil afterwards, line will not be incremented.
This commit is contained in:
Chris Smith 2017-11-01 17:15:49 +00:00
parent 4a2c22fb0a
commit 7bdd4aa5bd
3 changed files with 53 additions and 5 deletions

View File

@ -70,6 +70,22 @@ ftcsv.parse("apple,banana,carrot", ",", {loadFromString=true, headers=false})
local actual = ftcsv.parse("a,b,c\napple,banana,carrot", ",", options) local actual = ftcsv.parse("a,b,c\napple,banana,carrot", ",", options)
``` ```
- `rowFunc`
Applies a function to every row in the dataset. `rowFunc` will be passed a table containing the data for the row, and the row number. It should return a table which will be used in place of the original data table, or `nil` to remove the row entirely.
Ex: making field `a` uppercase
```lua
local options = {
loadFromString=true,
rowFunc=function(row, num)
row.a = string.upper(row.a)
return row
end
}
local actual = ftcsv.parse("a,b,c\napple,banana,carrot", ",", options)
```
- `headers` - `headers`
Set `headers` to `false` if the file you are reading doesn't have any headers. This will cause ftcsv to create indexed tables rather than a key-value tables for the output. Set `headers` to `false` if the file you are reading doesn't have any headers. This will cause ftcsv to create indexed tables rather than a key-value tables for the output.

27
ftcsv.lua Normal file → Executable file
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(inputString, inputLength, delimiter, i, headerField, fieldsToKeep, rowFunc)
-- keep track of my chars! -- keep track of my chars!
local currentChar, nextChar = sbyte(inputString, i), nil local currentChar, nextChar = sbyte(inputString, i), nil
@ -240,11 +240,21 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
-- print("fs:", fieldStart) -- print("fs:", fieldStart)
end end
-- incrememnt for new line -- check the column count
if fieldNum < initialLineCount then if fieldNum < initialLineCount then
error('ftcsv: too few columns in row ' .. lineNum) error('ftcsv: too few columns in row ' .. lineNum)
end end
lineNum = lineNum + 1
-- transform the row using the supplied function
if rowFunc then
outResults[lineNum] = rowFunc(outResults[lineNum], lineNum)
end
-- incrememnt for new line, if row transformation hasn't removed the current row.
if outResults[lineNum] then
lineNum = lineNum + 1
end
outResults[lineNum] = {} outResults[lineNum] = {}
fieldNum = 1 fieldNum = 1
fieldStart = i + 1 + skipChar fieldStart = i + 1 + skipChar
@ -284,6 +294,10 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
-- otherwise there might not be enough line -- otherwise there might not be enough line
elseif finalLineCount < initialLineCount then elseif finalLineCount < initialLineCount then
error('ftcsv: too few columns in row ' .. lineNum) error('ftcsv: too few columns in row ' .. lineNum)
-- else it is a fully-formed line, so do row transformation if required.
elseif rowFunc then
outResults[lineNum] = rowFunc(outResults[lineNum], lineNum)
end end
return outResults return outResults
@ -300,6 +314,7 @@ function ftcsv.parse(inputFile, delimiter, options)
local fieldsToKeep = nil local fieldsToKeep = nil
local loadFromString = false local loadFromString = false
local headerFunc local headerFunc
local rowFunc
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 +345,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.rowFunc ~= nil then
assert(type(options.rowFunc) == "function", "ftcsv only takes a function value for optional parameter 'rowFunc'. You passed in '" .. tostring(options.rowFunc) .. "' of type '" .. type(options.rowFunc) .. "'.")
rowFunc = options.rowFunc
end
end end
-- handle input via string or file! -- handle input via string or file!
@ -389,7 +408,7 @@ function ftcsv.parse(inputFile, delimiter, options)
end end
end end
local output = parseString(inputString, inputLength, delimiter, i, headerField, fieldsToKeep) local output = parseString(inputString, inputLength, delimiter, i, headerField, fieldsToKeep, rowFunc)
return output return output
end end

View File

@ -158,6 +158,19 @@ describe("csv features", function()
assert.are.same(expected, actual) assert.are.same(expected, actual)
end) end)
it("should make column A uppercase via rowFunc", function()
local expected = {}
expected[1] = {}
expected[1].a = "APPLE"
expected[1].b = "banana"
expected[1].c = "carrot"
local actual = ftcsv.parse("a,b,c\napple,banana,carrot", ",", {loadFromString=true, rowFunc=function(row)
row.a = string.upper(row.a)
return row
end})
assert.are.same(expected, actual)
end)
it("should handle encoding files", function() it("should handle encoding files", function()
local expected = {} local expected = {}
expected[1] = {} expected[1] = {}
@ -191,4 +204,4 @@ describe("csv features", function()
assert.are.same(expected, actual) assert.are.same(expected, actual)
end) end)
end) end)