UNPKG

oh-csv

Version:

Simple and parametrable CSV parser/encoder.

752 lines (693 loc) 24.1 kB
var assert = require('assert'); var Stream = require('readable-stream'); var csv = require('../src'); // Tools function getStreamObjs(stream, cb) { var objs = []; stream.on('readable', function () { var obj; do { obj = stream.read(); if(obj !== null) { objs.push(obj); } } while(obj !== null); }); stream.on('end', function () { cb(objs); }); } function getStreamText(stream, cb) { var text = ''; stream.on('readable', function () { var chunk; do { chunk = stream.read(); if(chunk !== null) { text += chunk.toString(); } } while(chunk !== null); }); stream.on('end', function () { cb(text); }); } function getStreamBuffer(stream, cb) { var buf = new Buffer(''); stream.on('readable', function () { var chunk; do { chunk = stream.read(); if(chunk) { buf = Buffer.concat([buf, chunk]); } } while(chunk !== null); }); stream.on('end', function () { cb(buf); }); } describe('csv parser', function() { describe('in array mode', function() { it('should work for csv with single seps config', function(done) { var parser = new csv.Parser({ esc: '\\', sep: ',', linesep: '\n' }); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'te,st1', 'anot,her test1'], [2, 'te,st2', 'anot,her test2'], [3, 'te,st3', 'anot,her test3'], [4, 'te,st4', 'anot,her test4'] ]); done(); }); parser.write('1,te\\,st1,anot\\,her test1\n'); parser.write('2,te\\,st2,anot\\,her test2\n'); parser.write('3,te\\,st3,anot\\,her test3\n'); parser.write('4,te\\,st4,anot\\,her test4\n'); parser.end(); }); it('should work for csv with csv config', function(done) { var parser = new csv.Parser(csv.csvOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', 'another test1'], [2, 'test2', 'another test2'], [3, 'test3', 'another test3'], [4, 'test4', 'another test4'] ]); done(); }); parser.write('1,test1,another test1\r\n'); parser.write('2,test2,another test2\r\n'); parser.write('3,test3,another test3\r\n'); parser.write('4,test4,another test4\r\n'); parser.end(); }); it('should work for csv with csv config with an empty first column', function(done) { var parser = new csv.Parser(csv.csvOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ ['', 'test1', 'another test1'], ['', 'test2', 'another test2'], ['', 'test3', 'another test3'], ['', 'test4', 'another test4'] ]); done(); }); parser.write(',test1,another test1\r\n'); parser.write(',test2,another test2\r\n'); parser.write(',test3,another test3\r\n'); parser.write(',test4,another test4\r\n'); parser.end(); }); it('should work for csv with csv config with an empty last column', function(done) { var parser = new csv.Parser(csv.csvOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', ''], [2, 'test2', ''], [3, 'test3', ''], [4, 'test4', ''] ]); done(); }); parser.write('1,test1,\r\n'); parser.write('2,test2,\r\n'); parser.write('3,test3,\r\n'); parser.write('4,test4,\r\n'); parser.end(); }); it('should work for csv with csv config with only 2 empty columns', function(done) { var parser = new csv.Parser(csv.csvOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ ['', ''], ['', ''], ['', ''], ['', ''] ]); done(); }); parser.write(',\r\n'); parser.write(',\r\n'); parser.write(',\r\n'); parser.write(',\r\n'); parser.end(); }); it('should work for csv with one field per line', function(done) { var parser = new csv.Parser(csv.csvOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ ['another test1'], ['another test2'], ['another test3'], ['another test4'] ]); done(); }); parser.write('another test1\r\n'); parser.write('another test2\r\n'); parser.write('another test3\r\n'); parser.write('another test4\r\n'); parser.end(); }); it('should work for csv with one field per line and no new line at the end', function(done) { var parser = new csv.Parser(csv.csvOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ ['another test1'], ['another test2'], ['another test3'], ['another test4'] ]); done(); }); parser.write('another test1\r\n'); parser.write('another test2\r\n'); parser.write('another test3\r\n'); parser.write('another test4'); parser.end(); }); it('should work for tsv with tsv config', function(done) { var parser = new csv.Parser(csv.tsvOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', 'another test1'], [2, 'test2', 'another test2'], [3, 'test3', 'another test3'], [4, 'test4', 'another test4'] ]); done(); }); parser.write('1\ttest1\tanother test1\r\n'); parser.write('2\ttest2\tanother test2\n'); parser.write('3\ttest3\tanother test3\r\n'); parser.write('4\ttest4\tanother test4\n'); parser.end(); }); it('should work for tsv with tsv config and complexer content', function(done) { var parser = new csv.Parser(csv.tsvOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', 'multi\r\nline\r\ntest1'], [2, 'test2', 'multi\r\nline\r\ntest1'], [3, 'test3', 'multi\r\nline\r\ntest1'], [4, 'test4', 'multi\r\nline\r\ntest1'] ]); done(); }); parser.write('1\ttest1\t"multi\r\nline\r\ntest1"\r\n'); parser.write('2\ttest2\t"multi\r\nline\r\ntest1"\n'); parser.write('3\ttest3\t"multi\r\nline\r\ntest1"\r\n'); parser.write('4\ttest4\t"multi\r\nline\r\ntest1"\n'); parser.end(); }); it('should work for csv with RFC csv config', function(done) { var parser = new csv.Parser(csv.csvRFCOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', 'another test1'], [2, 'test2', 'another test2'], [3, 'test3', 'another test3'], [4, 'test4', 'another test4'] ]); done(); }); parser.write('1,test1,another test1\r\n'); parser.write('2,test2,another test2\r\n'); parser.write('3,test3,another test3\r\n'); parser.write('4,test4,another test4\r\n'); parser.end(); }); it('should work with multichars config', function(done) { var parser = new csv.Parser({ linesep: 'aaaa', sep: 'bbbb', esc: 'cccc', quot: 'dddd' }); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', 'anobbther test1'], [2, 'test2', 'anobbbbther test2'], [3, 'test3', 'anobbther test3'], [4, 'test4', 'anobbbbther test4'] ]); done(); }); parser.write('1bbbbtest1bbbbanobbther test1aaaa'); parser.write('2bbbbtest2bbbbanoccccbbbbther test2aaaa'); parser.write('3bbbbtest3bbbbanobbther test3aaaa'); parser.write('4bbbbtest4bbbbanoccccbbbbther test4aaaa'); parser.end(); }); it('should work for csv when fields are given', function(done) { var parser = new csv.Parser({ fields: ['id', 'label', 'description'] }); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [{ id: 1, label: 'test1', description: 'another test1' },{ id: 2, label: 'test2', description: 'another test2' },{ id: 3, label: 'test3', description: 'another test3' },{ id: 4, label: 'test4', description: 'another test4' }]); done(); }); parser.write('1,test1,another test1\r\n'); parser.write('2,test2,another test2\r\n'); parser.write('3,test3,another test3\r\n'); parser.write('4,test4,another test4\r\n'); parser.end(); }); it('should work for csv with csv config and quotes', function(done) { var parser = new csv.Parser(csv.csvQuotOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', 'an "other" test1'], [2, 'test2', 'an "other" test2'], [3, 'test3', 'an "other" test3'], [4, 'test4', 'an "other" test4'] ]); done(); }); parser.write('1,"test1","an \\"other\\" test1"\r\n'); parser.write('2,"test2","an \\"other\\" test2"\r\n'); parser.write('3,"test3","an \\"other\\" test3"\r\n'); parser.write('4,"test4","an \\"other\\" test4"\r\n'); parser.end(); }); it('should work for csv with RFC csv config and quotes', function(done) { var parser = new csv.Parser(csv.csvRFCOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', 'an "other" test1'], [2, 'test2', 'an "other" test2'], [3, 'test3', 'an "other" test3'], [4, 'test4', 'an "other" test4'] ]); done(); }); parser.write('1,"test1","an ""other"" test1"\r\n'); parser.write('2,"test2","an ""other"" test2"\r\n'); parser.write('3,"test3","an ""other"" test3"\r\n'); parser.write('4,"test4","an ""other"" test4"\r\n'); parser.end(); }); it('should work for csv with RFC csv config and quotes but no new line at the end', function(done) { var parser = new csv.Parser(csv.csvRFCOpts); getStreamObjs(parser, function(objs) { assert.deepEqual(objs, [ [1, 'test1', 'an "other" test1'], [2, 'test2', 'an "other" test2'], [3, 'test3', 'an "other" test3'], [4, 'test4', 'an "other" test4'] ]); done(); }); parser.write('1,"test1","an ""other"" test1"\r\n'); parser.write('2,"test2","an ""other"" test2"\r\n'); parser.write('3,"test3","an ""other"" test3"\r\n'); parser.write('4,"test4","an ""other"" test4"'); parser.end(); }); it('should fail when a quoted field isn\'t closed', function(done) { var parser = new csv.Parser(csv.csvRFCOpts); parser.on('error', function(err) { assert.equal(err.message, 'Unclosed field detected.'); done(); }); parser.write('1,"test1","an ""other"" test1\r\n'); parser.write('2,test2,an other test2\r\n'); parser.write('3,test3,an other test3\r\n'); parser.write('4,test4,an other test4\r\n'); parser.end(); }); it('should fail when a quoted field is closed with another quote than the one that opened it', function(done) { var parser = new csv.Parser({ quote:['"','\''] }); parser.on('error', function(err) { assert.equal(err.message, 'Unclosed field detected.'); done(); }); parser.write('1,"test1","an ""other"" test1\r\n'); parser.write('2,"test2\',an other test2\r\n'); parser.write('3,test3,an other test3\r\n'); parser.write('4,test4,an other test4\r\n'); parser.end(); }); }); it('should work when new wasn\'t used', function() { assert.doesNotThrow(function() { csv.Parser() instanceof csv.Parser; }) }); it('should fail with no linesep is given', function() { assert.throws(function() { new csv.Parser({ linesep:[] }); }) }); it('should fail with no field sep is given', function() { assert.throws(function() { new csv.Parser({ sep:[] }); }) }); }); describe('csv encoder', function() { describe('with arrays', function() { describe('should work with csv config', function() { it('and simple input', function(done) { var encoder = new csv.Encoder(csv.csvOpts); getStreamText(encoder, function(text) { assert.equal(text, '1,test1,another test1\r\n' + '2,test2,another test2\r\n' + '3,test3,another test3\r\n' + '4,test4,another test4\r\n' ); done(); }); encoder.write([1, 'test1', 'another test1']); encoder.write([2, 'test2', 'another test2']); encoder.write([3, 'test3', 'another test3']); encoder.write([4, 'test4', 'another test4']); encoder.end(); }); it('with an empty first column', function(done) { var encoder = new csv.Encoder(csv.csvOpts); getStreamText(encoder, function(text) { assert.equal(text, ',test1,another test1\r\n' + ',test2,another test2\r\n' + ',test3,another test3\r\n' + ',test4,another test4\r\n' ); done(); }); encoder.write(['', 'test1', 'another test1']); encoder.write(['', 'test2', 'another test2']); encoder.write(['', 'test3', 'another test3']); encoder.write(['', 'test4', 'another test4']); encoder.end(); }); it('with an empty last column', function(done) { var encoder = new csv.Encoder(csv.csvOpts); getStreamText(encoder, function(text) { assert.equal(text, '1,test1,\r\n' + '2,test2,\r\n' + '3,test3,\r\n' + '4,test4,\r\n' ); done(); }); encoder.write([1, 'test1', '']); encoder.write([2, 'test2', '']); encoder.write([3, 'test3', '']); encoder.write([4, 'test4', '']); encoder.end(); }); it('with only 2 empty columns', function(done) { var encoder = new csv.Encoder(csv.csvOpts); getStreamText(encoder, function(text) { assert.equal(text, ',\r\n' + ',\r\n' + ',\r\n' + ',\r\n' ); done(); }); encoder.write(['', '']); encoder.write(['', '']); encoder.write(['', '']); encoder.write(['', '']); encoder.end(); }); it('only one field per line', function(done) { var encoder = new csv.Encoder(csv.csvOpts); getStreamText(encoder, function(text) { assert.equal(text, 'another test1\r\n' + 'another test2\r\n' + 'another test3\r\n' + 'another test4\r\n' ); done(); }); encoder.write(['another test1']); encoder.write(['another test2']); encoder.write(['another test3']); encoder.write(['another test4']); encoder.end(); }); it('and input needing escape', function(done) { var encoder = new csv.Encoder(csv.csvOpts); getStreamText(encoder, function(text) { assert.equal(text, '1,te\\,st1,ano\\,ther \\,test1\r\n' + '2,te\\,st2,ano\\,ther \\,test2\r\n' + '3,te\\,st3,ano\\,ther \\,test3\r\n' + '4,te\\,st4,ano\\,ther \\,test4\r\n' ); done(); }); encoder.write([1, 'te,st1', 'ano,ther ,test1']); encoder.write([2, 'te,st2', 'ano,ther ,test2']); encoder.write([3, 'te,st3', 'ano,ther ,test3']); encoder.write([4, 'te,st4', 'ano,ther ,test4']); encoder.end(); }); it('in object mode', function(done) { var encoder = new csv.Encoder({ fields: ['id', 'label', 'description'] }); getStreamText(encoder, function(text) { assert.equal(text, '1,te\\,st1,ano\\,ther \\,test1\r\n' + '2,te\\,st2,ano\\,ther \\,test2\r\n' + '3,te\\,st3,ano\\,ther \\,test3\r\n' + '4,te\\,st4,ano\\,ther \\,test4\r\n' ); done(); }); encoder.write({ id: 1, label: 'te,st1', description: 'ano,ther ,test1' }); encoder.write({ id: 2, label: 'te,st2', description: 'ano,ther ,test2' }); encoder.write({ id: 3, label: 'te,st3', description: 'ano,ther ,test3' }); encoder.write({ id: 4, label: 'te,st4', description: 'ano,ther ,test4' }); encoder.end(); }); }); describe('should work with tsv config', function() { it('and simple input', function(done) { var encoder = new csv.Encoder(csv.tsvOpts); getStreamText(encoder, function(text) { assert.equal(text, '1\ttest1\tanother test1\r\n' + '2\ttest2\tanother test2\r\n' + '3\ttest3\tanother test3\r\n' + '4\ttest4\tanother test4\r\n' ); done(); }); encoder.write([1, 'test1', 'another test1']); encoder.write([2, 'test2', 'another test2']); encoder.write([3, 'test3', 'another test3']); encoder.write([4, 'test4', 'another test4']); encoder.end(); }); it('and input needing escape', function(done) { var encoder = new csv.Encoder(csv.tsvOpts); getStreamText(encoder, function(text) { assert.equal(text, '1\t"te\tst1\n"\t"\r\nano\tther\r \ttest1"\r\n' + '2\t"te\tst2\n"\t"\r\nano\tther\r \ttest2"\r\n' + '3\t"te\tst3\n"\t"\r\nano\tther\r \ttest3"\r\n' + '4\t"te\tst4\n"\t"\r\nano\tther\r \ttest4"\r\n' ); done(); }); encoder.write([1, 'te\tst1\n', '\r\nano\tther\r \ttest1']); encoder.write([2, 'te\tst2\n', '\r\nano\tther\r \ttest2']); encoder.write([3, 'te\tst3\n', '\r\nano\tther\r \ttest3']); encoder.write([4, 'te\tst4\n', '\r\nano\tther\r \ttest4']); encoder.end(); }); }); describe('should work with the CSV RFC config', function() { it('with fields containing quotes', function(done) { var encoder = new csv.Encoder(csv.csvRFCOpts); getStreamText(encoder, function(text) { assert.equal(text, '1,tu,"""peux""",pas,"te""st"\r\n' + '2,tu,"""peux""",pas,"te""st"\r\n' + '3,tu,"""peux""",pas,"te""st"\r\n' + '4,tu,"""peux""",pas,"te""st"\r\n' ); done(); }); encoder.write([1, 'tu', '"peux"', 'pas', 'te"st']); encoder.write([2, 'tu', '"peux"', 'pas', 'te"st']); encoder.write([3, 'tu', '"peux"', 'pas', 'te"st']); encoder.write([4, 'tu', '"peux"', 'pas', 'te"st']); encoder.end(); }); it('with fields with new lines', function(done) { var encoder = new csv.Encoder(csv.csvRFCOpts); getStreamText(encoder, function(text) { assert.equal(text, '1,tu,"pe\r\nux","\r\npas\r\n",test\r\n' + '2,tu,"pe\r\nux","\r\npas\r\n",test\r\n' + '3,tu,"pe\r\nux","\r\npas\r\n",test\r\n' + '4,tu,"pe\r\nux","\r\npas\r\n",test\r\n' ); done(); }); encoder.write([1, 'tu', 'pe\r\nux', '\r\npas\r\n', 'test']); encoder.write([2, 'tu', 'pe\r\nux', '\r\npas\r\n', 'test']); encoder.write([3, 'tu', 'pe\r\nux', '\r\npas\r\n', 'test']); encoder.write([4, 'tu', 'pe\r\nux', '\r\npas\r\n', 'test']); encoder.end(); }); }); describe('should work with custom config', function() { it('introducing quotes and unix new lines', function(done) { var encoder = new csv.Encoder({ quote: '"', linesep: '\n' }); getStreamText(encoder, function(text) { assert.equal(text, '1,"\\"tu",peux,pas,"test\\""\n' + '2,"\\"tu",peux,pas,"test\\""\n' + '3,"\\"tu",peux,pas,"test\\""\n' + '4,"\\"tu",peux,pas,"test\\""\n' ); done(); }); encoder.write([1, '"tu', 'peux', 'pas', 'test"']); encoder.write([2, '"tu', 'peux', 'pas', 'test"']); encoder.write([3, '"tu', 'peux', 'pas', 'test"']); encoder.write([4, '"tu', 'peux', 'pas', 'test"']); encoder.end(); }); it('introducing exotic chars', function(done) { var encoder = new csv.Encoder({ quote: '~', sep: 'é', esc: '€', toEsc: ['€', '~', 'é', 'à'], linesep: 'à' }); getStreamText(encoder, function(text) { assert.equal(text, '1é~t€~u~é~p€éux~é~p€€s~é~t€àst~à' + '2é~t€~u~é~p€éux~é~p€€s~é~t€àst~à' + '3é~t€~u~é~p€éux~é~p€€s~é~t€àst~à' + '4é~t€~u~é~p€éux~é~p€€s~é~t€àst~à' ); done(); }); encoder.write([1, 't~u', 'péux', 'p€s', 'tàst']); encoder.write([2, 't~u', 'péux', 'p€s', 'tàst']); encoder.write([3, 't~u', 'péux', 'p€s', 'tàst']); encoder.write([4, 't~u', 'péux', 'p€s', 'tàst']); encoder.end(); }); it('introducing exotic chars', function(done) { var encoder = new csv.Encoder({ quote: '~', sep: 'é', esc: '€', linesep: 'à' }); getStreamText(encoder, function(text) { assert.equal(text, '1é~t€~u~é~péux~é~p€€s~é~tàst~à' + '2é~t€~u~é~péux~é~p€€s~é~tàst~à' + '3é~t€~u~é~péux~é~p€€s~é~tàst~à' + '4é~t€~u~é~péux~é~p€€s~é~tàst~à' ); done(); }); encoder.write([1, 't~u', 'péux', 'p€s', 'tàst']); encoder.write([2, 't~u', 'péux', 'p€s', 'tàst']); encoder.write([3, 't~u', 'péux', 'p€s', 'tàst']); encoder.write([4, 't~u', 'péux', 'p€s', 'tàst']); encoder.end(); }); }); }); it('should work when new wasn\'t used', function() { assert.doesNotThrow(function() { csv.Encoder() instanceof csv.Encoder; }) }); it('should fail with objects when no fields were given', function() { var encoder = new csv.Encoder(); assert.throws(function() { encoder.write({ id:1, label: 'test' }); }) }); }); describe('csv excel wrapper', function() { it('should work as expected', function(done) { var input = new Stream.PassThrough(); var output = csv.wrapForExcel(input); getStreamBuffer(output, function(buf) { assert.deepEqual(buf, Buffer.concat([ new Buffer([0xFF, 0xFE]), new Buffer( '1,test1,another test1\r\n' + '2,test2,another test2\r\n', 'ucs2' ) ]) ); done(); }); input.write('1,test1,another test1\r\n'); input.write('2,test2,another test2\r\n'); input.end(); }); });