diff --git a/README.md b/README.md
index 98acb8d..1863ec8 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,8 @@ Takes a string of CSV data and converts it to a 2 dimensional array of `[entries
- csv - the CSV string to parse
- options
- typed - infer types (default `false`)
+ - separator - the used separator, one character (default `,`)
+ - delimiter - the used field delimiter, one character (default `"`)
- reviver1 - a custom function to modify the output (default `(value) => value`)
*1 Values for `row` and `col` are 1-based.*
@@ -98,6 +100,8 @@ Takes a 2 dimensional array of `[entries][values]` and converts them to CSV
- array - the input array to stringify
- options
- eof - add a trailing newline at the end of file (default `true`)
+ - separator - the used separator, one character (default `,`)
+ - delimiter - the used field delimiter, one character (default `"`)
- replacer1 - a custom function to modify the values (default `(value) => value`)
*1 Values for `row` and `col` are 1-based.*
diff --git a/index.d.ts b/index.d.ts
index 647e301..2e1dcb3 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -3,6 +3,8 @@
*
* options
* - typed - infer types [false]
+ * - separator - use custom separator [,]
+ * - delimiter - use custom delimiter ["]
*
* @static
* @param {string} csv the CSV string to parse
@@ -16,6 +18,8 @@ export function parse(csv: string, options?: any, reviver?: Function): any[];
*
* options
* - eof - add a trailing newline at the end of file [true]
+ * - separator - use custom separator [,]
+ * - delimiter - use custom delimiter ["]
*
* @static
* @param {Array} array the input array to stringify
diff --git a/index.js b/index.js
index 967cc1c..f8bfc0e 100644
--- a/index.js
+++ b/index.js
@@ -3,6 +3,8 @@
*
* options
* - typed - infer types [false]
+ * - separator - use custom separator [,]
+ * - delimiter - use custom delimiter ["]
*
* @static
* @param {string} csv the CSV string to parse
@@ -20,7 +22,15 @@ export function parse (csv, options, reviver = v => v) {
ctx.col = 1
ctx.row = 1
- const lexer = /"|,|\r\n|\n|\r|[^",\r\n]+/y
+ ctx.options.delimiter = ctx.options.delimiter === undefined ? '"' : options.delimiter;
+ if(ctx.options.delimiter.length > 1 || ctx.options.delimiter.length === 0)
+ throw Error(`CSVError: delimiter must be one character [${ctx.options.separator}]`)
+
+ ctx.options.separator = ctx.options.separator === undefined ? ',' : options.separator;
+ if(ctx.options.separator.length > 1 || ctx.options.separator.length === 0)
+ throw Error(`CSVError: separator must be one character [${ctx.options.separator}]`)
+
+ const lexer = new RegExp(`${escapeRegExp(ctx.options.delimiter)}|${escapeRegExp(ctx.options.separator)}|\r\n|\n|\r|[^${escapeRegExp(ctx.options.delimiter)}${escapeRegExp(ctx.options.separator)}\r\n]+`, 'y')
const isNewline = /^(\r\n|\n|\r)$/
let matches = []
@@ -33,10 +43,10 @@ export function parse (csv, options, reviver = v => v) {
switch (state) {
case 0: // start of entry
switch (true) {
- case match === '"':
+ case match === ctx.options.delimiter:
state = 3
break
- case match === ',':
+ case match === ctx.options.separator:
state = 0
valueEnd(ctx)
break
@@ -53,7 +63,7 @@ export function parse (csv, options, reviver = v => v) {
break
case 2: // un-delimited input
switch (true) {
- case match === ',':
+ case match === ctx.options.separator:
state = 0
valueEnd(ctx)
break
@@ -69,7 +79,7 @@ export function parse (csv, options, reviver = v => v) {
break
case 3: // delimited input
switch (true) {
- case match === '"':
+ case match === ctx.options.delimiter:
state = 4
break
default:
@@ -80,11 +90,11 @@ export function parse (csv, options, reviver = v => v) {
break
case 4: // escaped or closing delimiter
switch (true) {
- case match === '"':
+ case match === ctx.options.delimiter:
state = 3
ctx.value += match
break
- case match === ',':
+ case match === ctx.options.separator:
state = 0
valueEnd(ctx)
break
@@ -114,6 +124,8 @@ export function parse (csv, options, reviver = v => v) {
*
* options
* - eof - add a trailing newline at the end of file [true]
+ * - separator - use custom separator [,]
+ * - delimiter - use custom delimiter ["]
*
* @static
* @param {Array} array the input array to stringify
@@ -129,19 +141,27 @@ export function stringify (array, options = {}, replacer = v => v) {
ctx.col = 1
ctx.output = ''
- const needsDelimiters = /"|,|\r\n|\n|\r/
+ ctx.options.delimiter = ctx.options.delimiter === undefined ? '"' : options.delimiter;
+ if(ctx.options.delimiter.length > 1 || ctx.options.delimiter.length === 0)
+ throw Error(`CSVError: delimiter must be one character [${ctx.options.separator}]`)
+
+ ctx.options.separator = ctx.options.separator === undefined ? ',' : options.separator;
+ if(ctx.options.separator.length > 1 || ctx.options.separator.length === 0)
+ throw Error(`CSVError: separator must be one character [${ctx.options.separator}]`)
+
+ const needsDelimiters = new RegExp(`${escapeRegExp(ctx.options.delimiter)}|${escapeRegExp(ctx.options.separator)}|\r\n|\n|\r`)
array.forEach((row, rIdx) => {
let entry = ''
ctx.col = 1
row.forEach((col, cIdx) => {
if (typeof col === 'string') {
- col = col.replace(/"/g, '""')
- col = needsDelimiters.test(col) ? `"${col}"` : col
+ col = col.replace(ctx.options.delimiter, `${ctx.options.delimiter}${ctx.options.delimiter}`)
+ col = needsDelimiters.test(col) ? `${ctx.options.delimiter}${col}${ctx.options.delimiter}` : col
}
entry += replacer(col, ctx.row, ctx.col)
if (cIdx !== row.length - 1) {
- entry += ','
+ entry += ctx.options.separator
}
ctx.col++
})
@@ -192,3 +212,8 @@ function inferType (value) {
return value
}
}
+
+/** @private */
+function escapeRegExp(str) {
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+}
\ No newline at end of file
diff --git a/index.min.js b/index.min.js
deleted file mode 100644
index 5208156..0000000
--- a/index.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-function b(t,n,i=e=>e){let e=Object.create(null);e.options=n||{},e.reviver=i,e.value="",e.entry=[],e.output=[],e.col=1,e.row=1;let l=/"|,|\r\n|\n|\r|[^",\r\n]+/y,a=/^(\r\n|\n|\r)$/,u=[],o="",r=0;for(;(u=l.exec(t))!==null;)switch(o=u[0],r){case 0:switch(!0){case o==='"':r=3;break;case o===",":r=0,s(e);break;case a.test(o):r=0,s(e),c(e);break;default:e.value+=o,r=2;break}break;case 2:switch(!0){case o===",":r=0,s(e);break;case a.test(o):r=0,s(e),c(e);break;default:throw r=4,Error(`CSVError: Illegal state [row:${e.row}, col:${e.col}]`)}break;case 3:switch(!0){case o==='"':r=4;break;default:r=3,e.value+=o;break}break;case 4:switch(!0){case o==='"':r=3,e.value+=o;break;case o===",":r=0,s(e);break;case a.test(o):r=0,s(e),c(e);break;default:throw Error(`CSVError: Illegal state [row:${e.row}, col:${e.col}]`)}break}return e.entry.length!==0&&(s(e),c(e)),e.output}function w(t,n={},i=e=>e){let e=Object.create(null);e.options=n,e.options.eof=e.options.eof!==void 0?e.options.eof:!0,e.row=1,e.col=1,e.output="";let l=/"|,|\r\n|\n|\r/;return t.forEach((a,u)=>{let o="";switch(e.col=1,a.forEach((r,f)=>{typeof r=="string"&&(r=r.replace(/"/g,'""'),r=l.test(r)?`"${r}"`:r),o+=i(r,e.row,e.col),f!==a.length-1&&(o+=","),e.col++}),!0){case e.options.eof:case(!e.options.eof&&u!==t.length-1):e.output+=`${o}
-`;break;default:e.output+=`${o}`;break}e.row++}),e.output}function s(t){let n=t.options.typed?p(t.value):t.value;t.entry.push(t.reviver(n,t.row,t.col)),t.value="",t.col++}function c(t){t.output.push(t.entry),t.entry=[],t.row++,t.col=1}function p(t){let n=/.\./;switch(!0){case t==="true":case t==="false":return t==="true";case n.test(t):return parseFloat(t);case isFinite(t):return parseInt(t);default:return t}}export{b as parse,w as stringify};
diff --git a/test/__test__/delimiter1.json b/test/__test__/delimiter1.json
new file mode 100644
index 0000000..7184214
--- /dev/null
+++ b/test/__test__/delimiter1.json
@@ -0,0 +1,22 @@
+{
+ "description": [
+ "When options.delimiter is set the parser uses this character as delimiter"
+ ],
+ "csv": [
+ "'123','456','789'",
+ "false,true,Test",
+ ""
+ ],
+ "json": [
+ [
+ "123",
+ "456",
+ "789"
+ ],
+ [
+ "false",
+ "true",
+ "Test"
+ ]
+ ]
+ }
\ No newline at end of file
diff --git a/test/__test__/delimiter2.json b/test/__test__/delimiter2.json
new file mode 100644
index 0000000..faa1eff
--- /dev/null
+++ b/test/__test__/delimiter2.json
@@ -0,0 +1,22 @@
+{
+ "description": [
+ "When options.delimiter is set the parser uses this character as delimiter (RegExp meta char)"
+ ],
+ "csv": [
+ "|123|,|456|,|789|",
+ "false,true,Test",
+ ""
+ ],
+ "json": [
+ [
+ "123",
+ "456",
+ "789"
+ ],
+ [
+ "false",
+ "true",
+ "Test"
+ ]
+ ]
+ }
\ No newline at end of file
diff --git a/test/__test__/delimiter3.json b/test/__test__/delimiter3.json
new file mode 100644
index 0000000..be8db51
--- /dev/null
+++ b/test/__test__/delimiter3.json
@@ -0,0 +1,22 @@
+{
+ "description": [
+ "When options.delimiter is set stringify uses this character as delimiter"
+ ],
+ "csv": [
+ "123,456,789",
+ "false,true,'Test,with separator'",
+ ""
+ ],
+ "json": [
+ [
+ "123",
+ "456",
+ "789"
+ ],
+ [
+ "false",
+ "true",
+ "Test,with separator"
+ ]
+ ]
+ }
\ No newline at end of file
diff --git a/test/__test__/separator1.json b/test/__test__/separator1.json
new file mode 100644
index 0000000..cac45d9
--- /dev/null
+++ b/test/__test__/separator1.json
@@ -0,0 +1,22 @@
+{
+ "description": [
+ "When options.separator is set the parser uses this character as separator"
+ ],
+ "csv": [
+ "123;456;789",
+ "false;true;\"Test;with separator\"",
+ ""
+ ],
+ "json": [
+ [
+ "123",
+ "456",
+ "789"
+ ],
+ [
+ "false",
+ "true",
+ "Test;with separator"
+ ]
+ ]
+ }
\ No newline at end of file
diff --git a/test/__test__/separator2.json b/test/__test__/separator2.json
new file mode 100644
index 0000000..87a9381
--- /dev/null
+++ b/test/__test__/separator2.json
@@ -0,0 +1,22 @@
+{
+ "description": [
+ "When options.separator is set the parser uses this character as separator (RegExp meta char)"
+ ],
+ "csv": [
+ "123|456|789",
+ "false|true|Test",
+ ""
+ ],
+ "json": [
+ [
+ "123",
+ "456",
+ "789"
+ ],
+ [
+ "false",
+ "true",
+ "Test"
+ ]
+ ]
+ }
\ No newline at end of file
diff --git a/test/options.spec.js b/test/options.spec.js
index 177094d..4ce54ef 100644
--- a/test/options.spec.js
+++ b/test/options.spec.js
@@ -10,6 +10,11 @@ const replacer1 = require('./__test__/replacer1.json')
const replacer2 = require('./__test__/replacer2.json')
const eof1 = require('./__test__/eof1.json')
const eof2 = require('./__test__/eof2.json')
+const separator1 = require('./__test__/separator1.json')
+const separator2 = require('./__test__/separator2.json')
+const delimiter1 = require('./__test__/delimiter1.json')
+const delimiter2 = require('./__test__/delimiter2.json')
+const delimiter3 = require('./__test__/delimiter3.json')
test('Reviver #1 - The reviver should append 1 to each value', (t) => {
const expect = reviver1.json
@@ -41,7 +46,7 @@ test('Typed #1 - When set to true the parser should infer the value types', (t)
test('Typed #2- When set to false the parser should not infer the value types', (t) => {
const expect = typed2.json
const actual = CSV.parse(typed2.csv.join('\n'), { typed: false })
-
+
t.deepEqual(actual, expect)
t.end()
@@ -82,3 +87,84 @@ test('EOF #2- When set to false the formatter should not include a newline at th
t.end()
})
+
+test('Separator #1 - When set the parser should use this character as separator', (t) => {
+ const expect = separator1.json
+ const actual = CSV.parse(separator1.csv.join('\n'), { separator: ';' })
+
+ t.deepEqual(actual, expect)
+
+ t.end()
+})
+
+test('Separator #2 - The parser accepts regular expression meta characters as separator', (t) => {
+ const expect = separator2.json
+ const actual = CSV.parse(separator2.csv.join('\n'), { separator: '|' })
+
+ t.deepEqual(actual, expect)
+
+ t.end()
+})
+
+test('Separator #3 - The separator must be one character', (t) => {
+ t.throws(
+ () => CSV.parse(separator1.csv.join('\n'), { separator: '' }),
+ /separator must be one character/
+ )
+ t.throws(
+ () => CSV.parse(separator1.csv.join('\n'), { separator: '==' }),
+ /separator must be one character/
+ )
+
+ t.end()
+})
+
+test('Separator #4 - When set stringify should use this character as separator', (t) => {
+ const expect = separator1.csv.join('\n')
+ const actual = CSV.stringify(separator1.json, { separator: ';' })
+
+ t.deepEqual(actual, expect)
+
+ t.end()
+})
+
+test('Delimiter #1 - When set the parser should use this character as delimiter', (t) => {
+ const expect = delimiter1.json
+ const actual = CSV.parse(delimiter1.csv.join('\n'), { delimiter: '\'' })
+
+ t.deepEqual(actual, expect)
+
+ t.end()
+})
+
+test('Delimiter #2 - The parser accepts regular expression meta characters as delimiter', (t) => {
+ const expect = delimiter2.json
+ const actual = CSV.parse(delimiter2.csv.join('\n'), { delimiter: '|' })
+
+ t.deepEqual(actual, expect)
+
+ t.end()
+})
+
+test('Delimiter #3 - The delimiter must be one character', (t) => {
+ t.throws(
+ () => CSV.parse(delimiter1.csv.join('\n'), { delimiter: '' }),
+ /delimiter must be one character/
+ )
+ t.throws(
+ () => CSV.parse(delimiter1.csv.join('\n'), { delimiter: '==' }),
+ /delimiter must be one character/
+ )
+
+ t.end()
+})
+
+
+test('Delimiter #4 - When set stringify should use this character as delimiter', (t) => {
+ const expect = delimiter3.csv.join('\n')
+ const actual = CSV.stringify(delimiter3.json, { delimiter: '\'' })
+
+ t.deepEqual(actual, expect)
+
+ t.end()
+})
\ No newline at end of file