Skip to content

Commit 6238c5a

Browse files
Rename noQuotes to onlyRequiredQuotes and fix encoding bug (#35)
1 parent 5be2f78 commit 6238c5a

File tree

4 files changed

+57
-38
lines changed

4 files changed

+57
-38
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ file:close()
143143
local output = ftcsv.encode(everyUser, ",", {fieldsToKeep={"Name", "Phone", "City"}})
144144
```
145145

146-
- `noQuotes`
146+
- `onlyRequiredQuotes`
147147

148-
if `noQuotes` is set to `true`, the output will not include quotes around fields.
148+
if `onlyRequiredQuotes` is set to `true`, the output will only include quotes around fields that are quotes, have newlines, or contain the delimter.
149149

150150
```lua
151151
local output = ftcsv.encode(everyUser, ",", {noQuotes=true})

ftcsv.lua

+19-16
Original file line numberDiff line numberDiff line change
@@ -645,15 +645,18 @@ local function delimitField(field)
645645
end
646646
end
647647

648-
local function delimitAndQuoteField(field)
649-
field = tostring(field)
650-
if field:find('"') then
651-
return '"' .. field:gsub('"', '""') .. '"'
652-
elseif field:find('[\n,]') then
653-
return '"' .. field .. '"'
654-
else
655-
return field
648+
local function generateDelimitAndQuoteField(delimiter)
649+
local generatedFunction = function(field)
650+
field = tostring(field)
651+
if field:find('"') then
652+
return '"' .. field:gsub('"', '""') .. '"'
653+
elseif field:find('[\n' .. delimiter .. ']') then
654+
return '"' .. field .. '"'
655+
else
656+
return field
657+
end
656658
end
659+
return generatedFunction
657660
end
658661

659662
local function escapeHeadersForLuaGenerator(headers)
@@ -681,7 +684,7 @@ local function csvLineGenerator(inputTable, delimiter, headers, options)
681684
delimiter .. [["' .. args.delimitField(args.t[i]["]]) ..
682685
[["]) .. '"\r\n']]
683686

684-
if options and options.noQuotes == true then
687+
if options and options.onlyRequiredQuotes == true then
685688
outputFunc = [[
686689
local args, i = ...
687690
i = i + 1;
@@ -696,8 +699,8 @@ local function csvLineGenerator(inputTable, delimiter, headers, options)
696699
arguments.t = inputTable
697700
-- we want to use the same delimitField throughout,
698701
-- so we're just going to pass it in
699-
if options and options.noQuotes == true then
700-
arguments.delimitField = delimitAndQuoteField
702+
if options and options.onlyRequiredQuotes == true then
703+
arguments.delimitField = generateDelimitAndQuoteField(delimiter)
701704
else
702705
arguments.delimitField = delimitField
703706
end
@@ -716,19 +719,19 @@ end
716719

717720
local function initializeOutputWithEscapedHeaders(escapedHeaders, delimiter, options)
718721
local output = {}
719-
if options and options.noQuotes == true then
722+
if options and options.onlyRequiredQuotes == true then
720723
output[1] = table.concat(escapedHeaders, delimiter) .. '\r\n'
721724
else
722725
output[1] = '"' .. table.concat(escapedHeaders, '"' .. delimiter .. '"') .. '"\r\n'
723726
end
724727
return output
725728
end
726729

727-
local function escapeHeadersForOutput(headers, options)
730+
local function escapeHeadersForOutput(headers, delimiter, options)
728731
local escapedHeaders = {}
729732
local delimitField = delimitField
730-
if options and options.noQuotes == true then
731-
delimitField = delimitAndQuoteField
733+
if options and options.onlyRequiredQuotes == true then
734+
delimitField = generateDelimitAndQuoteField(delimiter)
732735
end
733736
for i = 1, #headers do
734737
escapedHeaders[i] = delimitField(headers[i])
@@ -771,7 +774,7 @@ local function initializeGenerator(inputTable, delimiter, options)
771774
end
772775
validateHeaders(headers, inputTable)
773776

774-
local escapedHeaders = escapeHeadersForOutput(headers, options)
777+
local escapedHeaders = escapeHeadersForOutput(headers, delimiter, options)
775778
local output = initializeOutputWithEscapedHeaders(escapedHeaders, delimiter, options)
776779
return output, headers
777780
end

spec/feature_spec.lua

+35-19
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@ describe("csv features", function()
6161
assert.are.same(expected, actual)
6262
end)
6363

64-
it("should handle escaped doublequotes", function()
65-
local expected = {}
66-
expected[1] = {}
67-
expected[1].a = 'A"B""C'
68-
expected[1].b = 'A""B"C'
69-
expected[1].c = 'A"""B""C'
70-
local actual = ftcsv.parse('a;b;c\n"A""B""""C";"A""""B""C";"A""""""B""""C"', ";", {loadFromString=true})
71-
assert.are.same(expected, actual)
72-
end)
64+
it("should handle escaped doublequotes", function()
65+
local expected = {}
66+
expected[1] = {}
67+
expected[1].a = 'A"B""C'
68+
expected[1].b = 'A""B"C'
69+
expected[1].c = 'A"""B""C'
70+
local actual = ftcsv.parse('a;b;c\n"A""B""""C";"A""""B""C";"A""""""B""""C"', ";", {loadFromString=true})
71+
assert.are.same(expected, actual)
72+
end)
7373

7474
it("should handle renaming a field", function()
7575
local expected = {}
@@ -435,25 +435,41 @@ describe("csv features", function()
435435
it("should handle encoding files (str test)", function()
436436
local expected = '"a","b","c","d"\r\n"1","","foo","""quoted"""\r\n'
437437
output = ftcsv.encode({
438-
{ a = 1, b = '', c = 'foo', d = '"quoted"' };
439-
}, ',')
440-
assert.are.same(expected, output)
438+
{ a = 1, b = '', c = 'foo', d = '"quoted"' };
439+
}, ',')
440+
assert.are.same(expected, output)
441+
end)
442+
443+
it("should handle encoding files (str test) with other delimiter", function()
444+
local expected = '"a">"b">"c">"d"\r\n"1">"">"foo">"""quoted"""\r\n'
445+
output = ftcsv.encode({
446+
{ a = 1, b = '', c = 'foo', d = '"quoted"' };
447+
}, '>')
448+
assert.are.same(expected, output)
441449
end)
442450

443451
it("should handle encoding files without quotes (str test)", function()
444-
local expected = 'a,b,c,d\r\n1,,foo,"""quoted"""\r\n'
452+
local expected = 'a,b,c,d\r\n1,,"fo,o","""quoted"""\r\n'
453+
output = ftcsv.encode({
454+
{ a = 1, b = '', c = 'fo,o', d = '"quoted"' };
455+
}, ',', {onlyRequiredQuotes=true})
456+
assert.are.same(expected, output)
457+
end)
458+
459+
it("should handle encoding files without quotes with other delimiter (str test)", function()
460+
local expected = 'a>b>c>d\r\n1>>fo,o>"""quoted"""\r\n'
445461
output = ftcsv.encode({
446-
{ a = 1, b = '', c = 'foo', d = '"quoted"' };
447-
}, ',', {noQuotes=true})
448-
assert.are.same(expected, output)
462+
{ a = 1, b = '', c = 'fo,o', d = '"quoted"' };
463+
}, '>', {onlyRequiredQuotes=true})
464+
assert.are.same(expected, output)
449465
end)
450466

451467
it("should handle encoding files without quotes with certain fields to keep (str test)", function()
452468
local expected = "b,c\r\n,foo\r\n"
453469
output = ftcsv.encode({
454-
{ a = 1, b = '', c = 'foo', d = '"quoted"' };
455-
}, ',', {noQuotes=true, fieldsToKeep={"b", "c"}})
456-
assert.are.same(expected, output)
470+
{ a = 1, b = '', c = 'foo', d = '"quoted"' };
471+
}, ',', {onlyRequiredQuotes=true, fieldsToKeep={"b", "c"}})
472+
assert.are.same(expected, output)
457473
end)
458474

459475
it("should handle headers attempting to escape", function()

spec/parse_encode_spec.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ describe("csv encode without quotes", function()
9494
local jsonFile = loadFile("spec/json/" .. value .. ".json")
9595
local jsonDecode = cjson.decode(jsonFile)
9696
-- local parse = staecsv:ftcsv(contents, ",")
97-
local reEncodedNoQuotes = ftcsv.parse(ftcsv.encode(jsonDecode, ",", {noQuotes=true}), ",", {loadFromString=true})
97+
local reEncodedNoQuotes = ftcsv.parse(ftcsv.encode(jsonDecode, ",", {onlyRequiredQuotes=true}), ",", {loadFromString=true})
9898
-- local f = csv.openstring(contents, {separator=",", header=true})
9999
-- local parse = {}
100100
-- for fields in f:lines() do

0 commit comments

Comments
 (0)