├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── bench ├── .gitignore ├── 900000rows.csv ├── _bench.sh ├── a-csv.js ├── csv-stream.js ├── csv-streamer.js ├── csv-string.js ├── fast-csv.js ├── nodecsv.js ├── package.json └── twitter.csv ├── examples ├── fromStream.ts └── fromString.ts ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── CSV.ts ├── Parser.ts ├── Streamer.ts ├── __tests__ │ ├── csv.ts │ ├── sample.csv │ ├── sample.txt │ └── tsv.ts ├── index.ts └── types.ts ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const OFF = "off"; 2 | const ERROR = "error"; 3 | 4 | module.exports = { 5 | ignorePatterns: ["**/node_modules/*", "**/dist/*"], 6 | extends: [ 7 | "plugin:@typescript-eslint/recommended", 8 | "prettier/@typescript-eslint", 9 | "plugin:prettier/recommended" 10 | ], 11 | parser: "@typescript-eslint/parser", 12 | env: { 13 | node: true, 14 | jest: true 15 | }, 16 | plugins: ["import"], 17 | rules: { 18 | "prettier/prettier": [ERROR, { singleQuote: true }], 19 | "import/order": [ 20 | ERROR, 21 | { 22 | "newlines-between": "always", 23 | alphabetize: { order: "asc", caseInsensitive: true } 24 | } 25 | ] 26 | }, 27 | overrides: [ 28 | { 29 | files: ["bench/*"], 30 | rules: { 31 | "@typescript-eslint/no-var-requires": OFF 32 | } 33 | } 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | .env.test 68 | 69 | # parcel-bundler cache (https://parceljs.org/) 70 | .cache 71 | 72 | # next.js build output 73 | .next 74 | 75 | # nuxt.js build output 76 | .nuxt 77 | 78 | # vuepress build output 79 | .vuepress/dist 80 | 81 | # Serverless directories 82 | .serverless/ 83 | 84 | # FuseBox cache 85 | .fusebox/ 86 | 87 | # DynamoDB Local files 88 | .dynamodb/ 89 | 90 | # Visual Studio Code 91 | .vscode/* 92 | !.vscode/launch.json 93 | 94 | dist/ 95 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | script: 5 | - yarn lint 6 | - yarn build 7 | - yarn test 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Tests", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Nicolas Thouvenin 2 | 3 | This project is free software released under the MIT/X11 license: 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript CSV Strings 2 | 3 | [![Build Status](https://travis-ci.org/Inist-CNRS/node-csv-string.png?branch=master)](https://travis-ci.org/Inist-CNRS/node-csv-string) 4 | 5 | Parse and Stringify for CSV strings. 6 | 7 | - API similar to the JSON parser (`CSV.parse` and `CSV.stringify`). 8 | - Can also work row by row. 9 | - Can also be used to parse strings from readable streams (e.g. file streams). 10 | - Tolerant with the weird data 11 | - Written in TypeScript 12 | 13 | ```js 14 | import * as CSV from 'csv-string'; 15 | 16 | // with String 17 | const arr = CSV.parse('a,b,c\na,b,c'); 18 | const str = CSV.stringify(arr); 19 | 20 | // with Stream 21 | const stream = CSV.createStream(); 22 | stream.on('data', rows => { 23 | process.stdout.write(CSV.stringify(rows, ',')); 24 | }); 25 | process.stdin.pipe(stream); 26 | ``` 27 | 28 | ## Contributors 29 | 30 | - [Kael Shipman](https://github.com/kael-shipman) 31 | - [Mehul Mohan](https://github.com/mehulmpt) 32 | - [Hossam Magdy](https://github.com/hossam-magdy) 33 | - [Rich](https://github.com/rich-TIA) 34 | - [Rick Huizinga](https://github.com/rickhuizinga) 35 | - [Nicolas Thouvenin](https://github.com/touv) 36 | - [Stéphane Gully](https://github/kerphi) 37 | - [J. Baumbach](https://github.com/jbaumbach) 38 | - [Sam Hauglustaine](https://github.com/smhg) 39 | - [Rick Huizinga](https://github.com/rickhuizinga) 40 | - [doleksy1](https://github.com/doleksy1) 41 | - [François Parmentier](https://github.com/parmentf) 42 | 43 | ## Installation 44 | 45 | using [npm](http://npmjs.org): 46 | 47 | ```bash 48 | npm install csv-string 49 | ``` 50 | 51 | or [yarn](https://yarnpkg.com/) 52 | 53 | ```bash 54 | yarn add csv-string 55 | ``` 56 | 57 | ## API Documentation 58 | 59 | ### parse(input: String, [options: Object]): Object 60 | ### parse(input: string, [separator: string], [quote: string]): Object 61 | 62 | Converts a CSV string `input` to array output. 63 | 64 | 65 | Options : 66 | 67 | - `comma` **String** to indicate the CSV separator. (optional, default `,`) 68 | - `quote` **String** to indicate the CSV quote if need. (optional, default `"`) 69 | - `output` **String** choose 'objects' or 'tuples' to change output for Array or Object. (optional, default `tuples`) 70 | 71 | 72 | Example 1 : 73 | 74 | ```js 75 | const CSV = require('csv-string'); 76 | const parsedCsv = CSV.parse('a;b;c\nd;e;f', ';'); 77 | console.log(parsedCsv); 78 | ``` 79 | 80 | Output: 81 | 82 | ```json 83 | [ 84 | ["a", "b", "c"], 85 | ["d", "e", "f"] 86 | ] 87 | ``` 88 | 89 | Example 2 : 90 | 91 | ```js 92 | const CSV = require('csv-string'); 93 | const parsedCsv = CSV.parse('a,b,c\n1,2,3\n4,5,6', { output: 'objects' }); 94 | console.log(parsedCsv); 95 | ``` 96 | 97 | Output: 98 | 99 | ```json 100 | [ 101 | { a: '1', b: '2', c: '3' }, 102 | { a: '4', b: '5', c: '6' } 103 | ] 104 | ``` 105 | 106 | 107 | If separator parameter is not provided, it is automatically detected. 108 | 109 | ### stringify(input: Object, [separator: string]): string 110 | 111 | Converts object `input` to a CSV string. 112 | 113 | ```js 114 | import * as CSV from 'csv-string'; 115 | 116 | console.log(CSV.stringify(['a', 'b', 'c'])); 117 | console.log( 118 | CSV.stringify([ 119 | ['c', 'd', 'e'], 120 | ['c', 'd', 'e'] 121 | ]) 122 | ); 123 | console.log(CSV.stringify({ a: 'e', b: 'f', c: 'g' })); 124 | ``` 125 | 126 | Output: 127 | 128 | ```txt 129 | a,b,c 130 | 131 | c,d,e 132 | c,d,e 133 | 134 | e,f,g 135 | ``` 136 | 137 | ### detect(input: string): string 138 | 139 | Detects the best separator. 140 | 141 | ```js 142 | import * as CSV from 'csv-string'; 143 | 144 | console.log(CSV.detect('a,b,c')); 145 | console.log(CSV.detect('a;b;c')); 146 | console.log(CSV.detect('a|b|c')); 147 | console.log(CSV.detect('a\tb\tc')); 148 | ``` 149 | 150 | Output: 151 | 152 | ```txt 153 | , 154 | ; 155 | | 156 | \t 157 | ``` 158 | 159 | ### forEach(input: string, sep: string, quo: string, callback: function) 160 | 161 | ### forEach(input: string, sep: string, callback: function) 162 | 163 | ### forEach(input: string, callback: function) 164 | 165 | _callback(row: array, index: number): void_ 166 | 167 | Calls `callback` for each CSV row/line. The Array passed to callback contains the fields of the current row. 168 | 169 | ```js 170 | import * as CSV from 'csv-string'; 171 | 172 | const data = 'a,b,c\nd,e,f'; 173 | 174 | CSV.forEach(data, ',', function (row, index) { 175 | console.log('#' + index + ' : ', row); 176 | }); 177 | ``` 178 | 179 | Output: 180 | 181 | ```txt 182 | #0 : [ 'a', 'b', 'c' ] 183 | #1 : [ 'd', 'e', 'f' ] 184 | ``` 185 | 186 | ### read(input: string, sep: string, quo: string, callback: function): number 187 | 188 | ### read(input: string, sep: string, callback: function): number 189 | 190 | ### read(input: string, callback: function): number 191 | 192 | _callback(row: array): void_ 193 | 194 | Calls `callback` when a CSV row is read. The Array passed to callback contains the fields of the row. 195 | Returns the first offset after the row. 196 | 197 | ```js 198 | import * as CSV from 'csv-string'; 199 | 200 | const data = 'a,b,c\nd,e,f'; 201 | 202 | const index = CSV.read(data, ',', row => { 203 | console.log(row); 204 | }); 205 | 206 | console.log(data.slice(index)); 207 | ``` 208 | 209 | Output: 210 | 211 | ```txt 212 | [ 'a', 'b', 'c' ] 213 | d,e,f 214 | ``` 215 | 216 | ### readAll(input: string, sep: string, quo: string, callback: function): number 217 | 218 | ### readAll(input: string, sep: string, callback: function): number 219 | 220 | ### readAll(input: string, callback: function): number 221 | 222 | _callback(rows: array): void_ 223 | 224 | Calls `callback` when all CSV rows are read. The Array passed to callback contains the rows of the file. 225 | Returns the offset of the end of parsing (generally it's the end of the input string). 226 | 227 | ```js 228 | import * as CSV from 'csv-string'; 229 | 230 | const data = 'a,b,c\nd,e,f'; 231 | 232 | const index = CSV.readAll(data, row => { 233 | console.log(row); 234 | }); 235 | 236 | console.log('-' + data.slice(index) + '-'); 237 | ``` 238 | 239 | Output: 240 | 241 | ```txt 242 | [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ] ] 243 | -- 244 | ``` 245 | 246 | ### readChunk(input: string, sep: string, quo: string, callback: function): number 247 | 248 | ### readChunk(input: string, sep: string, callback: function): number 249 | 250 | ### readChunk(input: string, callback: function): number 251 | 252 | _callback(rows: array): void_ 253 | 254 | Calls `callback` when all CSV rows are read. The last row could be ignored, because the remainder could be in another chunk. 255 | The Array passed to callback contains the rows of the file. 256 | Returns the offset of the end of parsing. If the last row is ignored, the offset will point to the beginnning of the row. 257 | 258 | ```js 259 | import * as CSV from 'csv-string'; 260 | 261 | const data = 'a,b,c\nd,e'; 262 | 263 | const index = CSV.readChunk(data, row => { 264 | console.log(row); 265 | }); 266 | 267 | console.log('-' + data.slice(index) + '-'); 268 | ``` 269 | 270 | Output: 271 | 272 | ```txt 273 | [ [ 'a', 'b', 'c' ] ] 274 | -- 275 | ``` 276 | 277 | ### createStream(options: Object): WritableStream 278 | 279 | ### createStream(): WritableStream 280 | 281 | Create a writable stream for CSV chunk. Options are : 282 | 283 | - **separator** : To indicate the CSV separator. By default is auto (see the detect function) 284 | - quote\*\* : To indicate the CSVquote. 285 | 286 | Example : Read CSV file from the standard input. 287 | 288 | ```js 289 | const stream = CSV.createStream(); 290 | 291 | stream.on('data', row => { 292 | console.log(row); 293 | }); 294 | 295 | process.stdin.resume(); 296 | process.stdin.setEncoding('utf8'); 297 | process.stdin.pipe(stream); 298 | ``` 299 | 300 | ## Contribution 301 | 302 | - `clone` 303 | - `yarn install` 304 | - ... do the changes, write tests 305 | - `yarn test` (ensure all tests pass) 306 | - `yarn bench` (to check the performance impact) 307 | 308 | ## Related projects 309 | 310 | - 311 | - 312 | - 313 | 314 | ## Benchmark 315 | 316 | There is a quite basic benchmark to compare this project to other related ones, using file streams as input. See `./bench` for source code. 317 | 318 | ### the test 319 | 320 | ```bash 321 | yarn bench 322 | ``` 323 | 324 | ### the result 325 | 326 | for a test file with 949,044 rows 327 | 328 | | Package | Time | Output/Input similarity | 329 | | -------------- | --------- | ----------------------- | 330 | | a-csv | 6.01s | ~99% | 331 | | csv-stream | 6.64s | ~73% | 332 | | csv-streamer | 7.03s | ~79% | 333 | | **csv-string** | **6.53s** | **100%** | 334 | | fast-csv | 12.33s | 99.99% | 335 | | nodecsv | 7.10s | 100% | 336 | 337 | ## License 338 | 339 | [MIT/X11](https://github.com/Inist-CNRS/node-csv-string/blob/master/LICENSE) 340 | -------------------------------------------------------------------------------- /bench/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | yarn.lock 4 | .result.*.csv 5 | -------------------------------------------------------------------------------- /bench/_bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BASEDIR=$(dirname "$0") 4 | cd $BASEDIR 5 | RESULT_CSV_FILE_PREFIX=".result." 6 | REF_CSV="900000rows.csv" 7 | REF_LINES=$(cat ${REF_CSV} | wc -l) 8 | LIB_FILES="a-csv.js 9 | csv-stream.js 10 | csv-streamer.js 11 | csv-string.js 12 | fast-csv.js 13 | nodecsv.js" 14 | 15 | yarn 16 | 17 | for jsFile in ${LIB_FILES}; do 18 | resultCsvFile="${RESULT_CSV_FILE_PREFIX}${jsFile%.*}.csv" 19 | 20 | duration=$({ /usr/bin/time -f "%U" node ${jsFile} >${resultCsvFile}; } 2>&1) 21 | 22 | difference=$(diff ${REF_CSV} ${resultCsvFile} | wc -l) 23 | 24 | echo "${jsFile%.*} | ${duration}s | ${difference}/${REF_LINES}" 25 | done 26 | -------------------------------------------------------------------------------- /bench/a-csv.js: -------------------------------------------------------------------------------- 1 | const acsv = require('a-csv'); 2 | 3 | const CSV = require('..'); 4 | 5 | const FILE = `${__dirname}/twitter.csv`; 6 | 7 | const options = { 8 | delimiter: ',', 9 | headers: true, 10 | }; 11 | 12 | acsv.parse(FILE, options, (err, row, next) => { 13 | if (row !== null) { 14 | process.stdout.write(CSV.stringify(row)); 15 | next(); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /bench/csv-stream.js: -------------------------------------------------------------------------------- 1 | const { createReadStream } = require('fs'); 2 | 3 | const csv = require('csv-stream'); 4 | 5 | const CSV = require('..'); 6 | 7 | const FILE = `${__dirname}/twitter.csv`; 8 | 9 | const csvStream = csv.createStream(); 10 | 11 | createReadStream(FILE) 12 | .pipe(csvStream) 13 | .on('data', (data) => { 14 | process.stdout.write(CSV.stringify(data)); 15 | }); 16 | -------------------------------------------------------------------------------- /bench/csv-streamer.js: -------------------------------------------------------------------------------- 1 | const { createReadStream } = require('fs'); 2 | 3 | const CSVStream = require('csv-streamer').Reader; 4 | 5 | const CSV = require('..'); 6 | 7 | const FILE = `${__dirname}/twitter.csv`; 8 | const csv = new CSVStream({ headers: true }); 9 | 10 | csv.on('data', (line) => { 11 | process.stdout.write(CSV.stringify(line)); 12 | }); 13 | createReadStream(FILE).pipe(csv); 14 | -------------------------------------------------------------------------------- /bench/csv-string.js: -------------------------------------------------------------------------------- 1 | const { createReadStream } = require('fs'); 2 | 3 | const CSV = require('..'); 4 | 5 | const FILE = `${__dirname}/twitter.csv`; 6 | const stream = CSV.createStream(); 7 | stream.on('data', (row) => { 8 | process.stdout.write(CSV.stringify(row)); 9 | }); 10 | 11 | createReadStream(FILE).pipe(stream); 12 | -------------------------------------------------------------------------------- /bench/fast-csv.js: -------------------------------------------------------------------------------- 1 | const { createReadStream } = require('fs'); 2 | 3 | const fastcsv = require('fast-csv'); 4 | 5 | const CSV = require('..'); 6 | 7 | const FILE = `${__dirname}/twitter.csv`; 8 | 9 | createReadStream(FILE) 10 | .pipe(fastcsv.parse({ headers: true })) 11 | .on('data', (data) => { 12 | process.stdout.write(CSV.stringify(data)); 13 | }); 14 | -------------------------------------------------------------------------------- /bench/nodecsv.js: -------------------------------------------------------------------------------- 1 | const { createReadStream } = require('fs'); 2 | 3 | const nodecsv = require('csv'); 4 | 5 | const CSV = require('..'); 6 | 7 | const FILE = `${__dirname}/twitter.csv`; 8 | 9 | const parser = nodecsv.parse(); 10 | parser.on('readable', function () { 11 | let record; 12 | while ((record = parser.read())) { 13 | process.stdout.write(CSV.stringify(record)); 14 | } 15 | }); 16 | 17 | createReadStream(FILE).pipe(parser); 18 | -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csv-string-bench", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "dependencies": { 8 | "a-csv": "^2.0.0", 9 | "csv": "^5.3.2", 10 | "csv-stream": "^0.2.0", 11 | "csv-streamer": "^2.0.3", 12 | "fast-csv": "^4.1.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /bench/twitter.csv: -------------------------------------------------------------------------------- 1 | 900000rows.csv -------------------------------------------------------------------------------- /examples/fromStream.ts: -------------------------------------------------------------------------------- 1 | import * as CSV from '..'; 2 | 3 | const stream = CSV.createStream(); 4 | 5 | stream.on('data', (rows) => { 6 | process.stdout.write(CSV.stringify(rows, ',')); 7 | }); 8 | 9 | process.stdin.resume(); 10 | process.stdin.setEncoding('utf8'); 11 | process.stdin.pipe(stream); 12 | -------------------------------------------------------------------------------- /examples/fromString.ts: -------------------------------------------------------------------------------- 1 | import * as CSV from '..'; 2 | 3 | let buffer = ''; 4 | 5 | process.stdin.resume(); 6 | process.stdin.setEncoding('utf8'); 7 | process.stdin.on('data', (chunk) => { 8 | buffer += chunk.toString(); 9 | }); 10 | process.stdin.on('end', () => { 11 | CSV.readAll(buffer, ',', (rows) => { 12 | rows.forEach((item) => { 13 | process.stdout.write(CSV.stringify(item)); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csv-string", 3 | "version": "4.1.1", 4 | "description": "PARSE and STRINGIFY for CSV strings. It's like JSON object but for CSV. It can also work row by row. And, if can parse strings, it can be use to parse files or streams too.", 5 | "keywords": [ 6 | "csv", 7 | "parser", 8 | "string", 9 | "generator" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/Inist-CNRS/node-csv-string" 14 | }, 15 | "license": "MIT", 16 | "author": "Nicolas Thouvenin ", 17 | "contributors": [ 18 | { 19 | "name": "Mehul Mohan", 20 | "url": "https://github.com/mehulmpt" 21 | }, 22 | { 23 | "name": "Hossam Magdy", 24 | "url": "https://github.com/hossam-magdy" 25 | }, 26 | { 27 | "name": "Rich", 28 | "url": "https://github.com/rich-TIA" 29 | }, 30 | { 31 | "name": "Stéphane Gully", 32 | "url": "https://github.com/kerphi" 33 | }, 34 | { 35 | "name": "J Baumbach", 36 | "url": "https://github.com/jbaumbach" 37 | }, 38 | { 39 | "name": "Sam Hauglustaine", 40 | "url": "https://github.com/smhg" 41 | }, 42 | { 43 | "name": "Rick Huizinga", 44 | "url": "https://github.com/rickhuizinga" 45 | }, 46 | { 47 | "name": "François Parmentier", 48 | "url": "https://github.com/parmentf" 49 | } 50 | ], 51 | "main": "./dist/index.js", 52 | "types": "./dist/index.d.ts", 53 | "files": [ 54 | "/dist" 55 | ], 56 | "scripts": { 57 | "build": "rm -rf ./dist && tsc", 58 | "lint": "eslint --ext js,ts .", 59 | "test": "jest --coverage", 60 | "bench": "./bench/_bench.sh", 61 | "preversion": "npm run build", 62 | "postversion": "git push && git push --tags" 63 | }, 64 | "dependencies": {}, 65 | "devDependencies": { 66 | "@types/jest": "^25.1.4", 67 | "@types/mocha": "^7.0.2", 68 | "@types/node": "^13.9.5", 69 | "@typescript-eslint/eslint-plugin": "^2.25.0", 70 | "@typescript-eslint/parser": "^2.25.0", 71 | "eslint": "^6.8.0", 72 | "eslint-config-prettier": "^6.10.1", 73 | "eslint-plugin-import": "^2.20.2", 74 | "eslint-plugin-prettier": "^3.1.2", 75 | "jest": "^25.2.3", 76 | "prettier": "^2.0.2", 77 | "ts-jest": "^25.2.1", 78 | "ts-node": "^8.8.1", 79 | "typescript": "^3.8.3" 80 | }, 81 | "engines": { 82 | "node": ">=12.0" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/CSV.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from './Parser'; 2 | import { Streamer } from './Streamer'; 3 | import { 4 | Comma, 5 | ForEachCallback, 6 | LineBreak, 7 | PristineInput, 8 | Quote, 9 | ReadAllCallback, 10 | ReadCallback, 11 | Value, 12 | ParseOptions, 13 | } from './types'; 14 | 15 | const EOL: LineBreak = '\r\n'; 16 | const SEPARATOR: Comma = ','; 17 | 18 | const quoteCharReqex = new RegExp('"', 'g'); 19 | const specialCharReqex = new RegExp('["\r\n]', 'g'); 20 | const _shouldBeQuoted = (value: string, sep: string): boolean => 21 | value.search(specialCharReqex) >= 0 || value.includes(sep); 22 | const _quoteIfRquired = (value: string, sep: string): string => 23 | _shouldBeQuoted(value, sep) 24 | ? '"' + value.replace(quoteCharReqex, '""') + '"' 25 | : value; 26 | const _stringifySingleValue = (item: PristineInput): string => { 27 | if (item === 0) { 28 | item = '0'; 29 | } else if (item === undefined || item === null) { 30 | item = ''; 31 | } 32 | if (typeof item != 'string') { 33 | const s = item.toString(); 34 | if (s == '[object Object]') { 35 | item = JSON.stringify(item); 36 | if (item == '{}') { 37 | item = ''; 38 | } 39 | } else { 40 | item = s; 41 | } 42 | } 43 | return item; 44 | }; 45 | const reducer = ( 46 | item: PristineInput, 47 | memo: PristineInput | undefined, 48 | sep: Comma, 49 | prependSep?: boolean 50 | ): Value => { 51 | item = _stringifySingleValue(item); 52 | return ( 53 | (memo !== undefined || prependSep ? `${memo}${sep}` : '') + 54 | _quoteIfRquired(item, sep) 55 | ); 56 | }; 57 | 58 | const detect = (input: string): Comma => { 59 | const separators = [',', ';', '|', '\t']; 60 | const idx = separators 61 | .map((separator) => input.indexOf(separator)) 62 | .reduce((prev, cur) => 63 | prev === -1 || (cur !== -1 && cur < prev) ? cur : prev 64 | ); 65 | return (input[idx] || ',') as Comma; 66 | }; 67 | 68 | const stringify = (input?: PristineInput, sep: Comma = SEPARATOR): string => { 69 | let ret: string | undefined; 70 | sep = sep || SEPARATOR; 71 | if (Array.isArray(input)) { 72 | if (input.length === 0) { 73 | ret = EOL; 74 | } else if (!Array.isArray(input[0])) { 75 | for (let loop = 0; loop < input.length; loop++) { 76 | ret = reducer(input[loop], ret, sep, loop > 0); 77 | } 78 | ret += EOL; 79 | } else if (Array.isArray(input[0])) { 80 | ret = input.map((item) => stringify(item, sep)).join(''); 81 | } 82 | } else if (typeof input == 'object') { 83 | for (const key in input) { 84 | if (input.hasOwnProperty(key)) { 85 | ret = reducer(input[key], ret, sep); 86 | } 87 | } 88 | ret += EOL; 89 | } else { 90 | ret = reducer(input, ret, sep) + EOL; 91 | } 92 | return ret as string; 93 | }; 94 | 95 | function parse(input: string, sep?: Comma, quo?: Quote): Value[][]; 96 | function parse(input: string, opts?: Partial): Value[][]; 97 | function parse( 98 | input: string, 99 | opts: Partial & { output: 'objects' } 100 | ): { [k: string]: Value }[]; 101 | function parse( 102 | input: string, 103 | sepOrOpts?: Comma | Partial, 104 | quo?: Quote 105 | ): Value[][] | { [k: string]: Value }[] { 106 | // Create an options hash, using positional parameters or the passed in options hash for values 107 | const opts: Partial = typeof sepOrOpts === "object" ? sepOrOpts : {}; 108 | if (typeof sepOrOpts === "string") { 109 | opts.comma = sepOrOpts as Comma; 110 | } 111 | if (quo) { 112 | opts.quote = quo as Quote; 113 | } 114 | 115 | // try to detect the separator if not provided 116 | if (opts.comma === undefined) { 117 | opts.comma = detect(input); 118 | } 119 | 120 | // Clean characters, if necessary 121 | // TODO: We should probably throw an error here to signal bad input to the user 122 | if (opts.comma) { 123 | opts.comma = opts.comma[0] as Comma; 124 | } 125 | if (opts.quote) { 126 | opts.quote = opts.quote[0] as Quote; 127 | } 128 | 129 | const csv = new Parser(input, opts.comma, opts.quote); 130 | return csv.File(opts.output); 131 | }; 132 | 133 | function read(input: string, callback: ReadCallback): number; 134 | function read(input: string, sep: Comma, callback: ReadCallback): number; 135 | function read( 136 | input: string, 137 | sep: Comma, 138 | quo: Quote, 139 | callback: ReadCallback 140 | ): number; 141 | function read( 142 | input: string, 143 | sep: Comma | ReadCallback, 144 | quo?: Quote | ReadCallback, 145 | callback?: ReadCallback 146 | ): number { 147 | if (callback === undefined) { 148 | if (quo === undefined) { 149 | // arguments.length < 3) { 150 | if (typeof sep !== 'function') { 151 | throw Error('Last/second argument is not a callback'); 152 | } 153 | callback = sep; 154 | sep = ','; 155 | } else { 156 | // arguments.length < 4) { 157 | if (typeof quo !== 'function') { 158 | throw Error('Last/third argument is not a callback'); 159 | } 160 | callback = quo; 161 | quo = '"'; 162 | } 163 | } 164 | const csv = new Parser(input, sep as Comma, quo as Quote); 165 | const fields = csv.Row(); 166 | callback(fields); 167 | return csv.pointer; 168 | } 169 | 170 | function forEach(input: string, callback: ForEachCallback): void; 171 | function forEach(input: string, sep: Comma, callback: ForEachCallback): void; 172 | function forEach( 173 | input: string, 174 | sep: Comma, 175 | quo: Quote, 176 | callback: ForEachCallback 177 | ): void; 178 | function forEach( 179 | input: string, 180 | sep: Comma | ForEachCallback, 181 | quo?: Quote | ForEachCallback, 182 | callback?: ForEachCallback 183 | ): void { 184 | if (callback === undefined) { 185 | if (quo === undefined) { 186 | // arguments.length < 3) { 187 | if (typeof sep !== 'function') { 188 | throw Error('Last/second argument is not a callback'); 189 | } 190 | callback = sep; 191 | sep = ','; 192 | } else { 193 | // arguments.length < 4) { 194 | if (typeof quo !== 'function') { 195 | throw Error('Last/third argument is not a callback'); 196 | } 197 | callback = quo; 198 | quo = '"'; 199 | } 200 | } 201 | let i = 0; 202 | let s = 0; 203 | let r: number; 204 | while ( 205 | (r = read(input.slice(s), sep as Comma, quo as Quote, (fields) => 206 | (callback as ForEachCallback)(fields, i++) 207 | )) 208 | ) { 209 | s += r; 210 | } 211 | } 212 | 213 | function readAll(input: string, callback: ReadAllCallback): number; 214 | function readAll(input: string, sep: Comma, callback: ReadAllCallback): number; 215 | function readAll( 216 | input: string, 217 | sep: Comma, 218 | quo: Quote, 219 | callback: ReadAllCallback 220 | ): number; 221 | function readAll( 222 | input: string, 223 | sep: Comma | ReadAllCallback, 224 | quo?: Quote | ReadAllCallback, 225 | callback?: ReadAllCallback 226 | ): number { 227 | if (callback === undefined) { 228 | if (quo === undefined) { 229 | // arguments.length < 3) { 230 | if (typeof sep !== 'function') { 231 | throw Error('Last/second argument is not a callback'); 232 | } 233 | callback = sep; 234 | sep = ','; 235 | } else { 236 | // arguments.length < 4) { 237 | if (typeof quo !== 'function') { 238 | throw Error('Last/third argument is not a callback'); 239 | } 240 | callback = quo; 241 | quo = '"'; 242 | } 243 | } 244 | const csv = new Parser(input, sep as Comma, quo as Quote); 245 | const rows = csv.File(); 246 | callback(rows); 247 | return csv.pointer; 248 | } 249 | 250 | function readChunk(input: string, callback: ReadAllCallback): number; 251 | function readChunk( 252 | input: string, 253 | sep: Comma, 254 | callback: ReadAllCallback 255 | ): number; 256 | function readChunk( 257 | input: string, 258 | sep: Comma, 259 | quo: Quote, 260 | callback: ReadAllCallback 261 | ): number; 262 | function readChunk( 263 | input: string, 264 | sep: Comma | ReadAllCallback, 265 | quo?: Quote | ReadAllCallback, 266 | callback?: ReadAllCallback 267 | ): number { 268 | if (callback === undefined) { 269 | if (quo === undefined) { 270 | // arguments.length < 3) { 271 | if (typeof sep !== 'function') { 272 | throw Error('Last/second argument is not a callback'); 273 | } 274 | callback = sep; 275 | sep = ','; 276 | } else { 277 | // arguments.length < 4) { 278 | if (typeof quo !== 'function') { 279 | throw Error('Last/third argument is not a callback'); 280 | } 281 | callback = quo; 282 | quo = '"'; 283 | } 284 | } 285 | const csv = new Parser(input, sep as Comma, quo as Quote); 286 | const rows = csv.File(); 287 | let ret = 0; 288 | if (csv.pointer < input.length) { 289 | ret = csv.pointer; 290 | } else { 291 | rows.pop(); 292 | ret = csv.linePointer; 293 | } 294 | callback(rows); 295 | return ret; 296 | } 297 | 298 | const fetch = (input: string, sep?: Comma, quo?: Quote): Value[] => { 299 | // TODO 300 | let output: Value[] | undefined; 301 | read(input, sep as Comma, quo as Quote, (fields) => { 302 | output = fields; 303 | }); 304 | return output as Value[]; 305 | }; 306 | 307 | const createStream = (options?: { 308 | separator?: Comma; 309 | quote?: Quote; 310 | }): Streamer => new Streamer(options); 311 | 312 | export { 313 | EOL as eol, 314 | SEPARATOR as separator, 315 | detect, 316 | stringify, 317 | parse, 318 | read, 319 | forEach, 320 | readAll, 321 | readChunk, 322 | fetch, 323 | createStream, 324 | }; 325 | -------------------------------------------------------------------------------- /src/Parser.ts: -------------------------------------------------------------------------------- 1 | /* 2 | file: row + EOF; 3 | row: value (Comma value)* (LineBreak | EOF); 4 | value: SimpleValue | QuotedValue; 5 | Comma: ','; 6 | LineBreak: '\r'?'\n' | '\r'; 7 | SimpleValue: ~(',' | '\r' | '\n' | '"')+; 8 | QuotedValue: Residue '"' ('""' | ~'"')* '"' Residue; 9 | Residue: (' ' | '\t' | '\f')* 10 | */ 11 | 12 | import { LineBreak, Comma, Quote, Residue, Value } from './types'; 13 | 14 | export class Parser { 15 | input!: string; 16 | quote!: Quote; 17 | comma!: Comma; 18 | pointer!: number; 19 | linePointer!: number; 20 | _residueRegExp!: RegExp; 21 | _simpleValueRegExp!: RegExp; 22 | _replaceQuoteRegExp!: RegExp; 23 | 24 | constructor(input: string, comma?: Comma, quote?: Quote) { 25 | if (!(this instanceof Parser)) { 26 | return new Parser(input, comma); 27 | } 28 | this.input = input; 29 | this.pointer = 0; 30 | this.linePointer = 0; 31 | this.comma = (comma && (comma[0] as Comma)) || ','; 32 | this.quote = (quote && (quote[0] as Quote)) || '"'; 33 | // initialize RegExp Object 34 | let residueChars = 35 | ' \f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000'; 36 | if (this.comma !== '\t') { 37 | residueChars += '\t'; 38 | } 39 | this._residueRegExp = new RegExp(`[^${residueChars}]`); 40 | // TODO: `(${this.comma}|\r\n)` instead? 41 | this._simpleValueRegExp = new RegExp(`[${this.comma}\r\n]`); 42 | this._replaceQuoteRegExp = new RegExp(this.quote + this.quote, 'g'); 43 | } 44 | 45 | File(output: "objects"): { [k: string]: Value }[]; 46 | File(output?: "tuples"): Value[][]; 47 | File(output?: "tuples" | "objects"): { [k: string]: Value }[] | Value[][]; 48 | File(output?: "tuples" | "objects"): { [k: string]: Value }[] | Value[][] { 49 | const rows: Value[][] = []; 50 | while (true) { 51 | const tempointer = this.pointer; 52 | const row: Value[] = this.Row(); 53 | if (row.length > 0) { 54 | this.linePointer = tempointer; 55 | rows.push(row); 56 | } else { 57 | if (this.linePointer && this.pointer !== this.input.length) { 58 | rows.pop(); 59 | this.pointer = this.linePointer; 60 | } 61 | break; 62 | } 63 | if (this.EOF()) { 64 | if (this.linePointer && this.pointer !== this.input.length) { 65 | rows.pop(); 66 | this.pointer = this.linePointer; 67 | } 68 | break; 69 | } 70 | } 71 | 72 | if (output && output === "objects") { 73 | if (rows.length === 0) { 74 | return []; 75 | } 76 | const headers = rows.shift()!.map(k => k.trim()); 77 | return rows.map(row => headers.reduce<{ [k: string]: Value }>((data, k, i) => { 78 | data[k] = row[i] 79 | return data; 80 | }, {})); 81 | } else { 82 | return rows; 83 | } 84 | } 85 | 86 | Row(): Value[] { 87 | const row: Value[] = []; 88 | while (true) { 89 | row.push(this.Value()); 90 | if (this.Comma()) { 91 | continue; 92 | } 93 | if (this.LineBreak() || this.EOF()) { 94 | return row; 95 | } else { 96 | row.pop(); 97 | return row; 98 | } 99 | } 100 | } 101 | 102 | private Value(): Value { 103 | const residue = this.Residue(); 104 | const quotedvalue = this.QuotedValue(); 105 | if (quotedvalue) { 106 | const value = quotedvalue 107 | .slice(1, -1) 108 | .replace(this._replaceQuoteRegExp, this.quote); 109 | this.Residue(); 110 | return value; 111 | } 112 | const simplevalue = this.SimpleValue(); 113 | if (simplevalue) { 114 | return residue ? residue + simplevalue : simplevalue; 115 | } 116 | return ''; 117 | } 118 | 119 | private Comma(): Comma | undefined { 120 | if ( 121 | this.input.slice(this.pointer, this.pointer + this.comma.length) === 122 | this.comma 123 | ) { 124 | this.pointer += this.comma.length; 125 | return this.comma; 126 | } 127 | } 128 | 129 | private LineBreak(): LineBreak | undefined { 130 | if (this.input.slice(this.pointer, this.pointer + 2) === '\r\n') { 131 | this.pointer += 2; 132 | return '\r\n'; 133 | } 134 | if (this.input.charAt(this.pointer) === '\n') { 135 | this.pointer += 1; 136 | return '\n'; 137 | } 138 | if (this.input.charAt(this.pointer) === '\r') { 139 | this.pointer += 1; 140 | return '\r'; 141 | } 142 | } 143 | 144 | private SimpleValue(): Value | undefined { 145 | let value = ''; 146 | const index = this.input 147 | .slice(this.pointer) 148 | .search(this._simpleValueRegExp); 149 | if (this.input.charAt(this.pointer) === this.quote) { 150 | return; 151 | } else if (index === -1) { 152 | value = this.input.slice(this.pointer); 153 | } else if (index === 0) { 154 | return; 155 | } else { 156 | value = this.input.slice(this.pointer, this.pointer + index); 157 | } 158 | this.pointer += value.length; 159 | return value; 160 | } 161 | 162 | private QuotedValue(): Value | undefined { 163 | if (this.input.charAt(this.pointer) === this.quote) { 164 | let searchIndex; 165 | let index = 1; 166 | while (true) { 167 | searchIndex = this.input.slice(this.pointer + index).search(this.quote); 168 | if (searchIndex === -1) { 169 | return; 170 | } 171 | if ( 172 | this.input.charAt(this.pointer + index + searchIndex + 1) === 173 | this.quote 174 | ) { 175 | index += searchIndex + 2; 176 | continue; 177 | } 178 | const value = this.input.slice( 179 | this.pointer, 180 | this.pointer + index + searchIndex + 1 181 | ); 182 | this.pointer += value.length; 183 | return value; 184 | } 185 | } 186 | } 187 | 188 | private EOF(): boolean { 189 | return this.pointer >= this.input.length; 190 | } 191 | 192 | private Residue(): Residue { 193 | let value = ''; 194 | const index = this.input.slice(this.pointer).search(this._residueRegExp); 195 | if (index === -1) { 196 | value = this.input.slice(this.pointer); 197 | } else if (index === 0) { 198 | return ''; 199 | } else { 200 | value = this.input.slice(this.pointer, this.pointer + index); 201 | } 202 | this.pointer += value.length; 203 | return value; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/Streamer.ts: -------------------------------------------------------------------------------- 1 | import { Transform, TransformCallback } from 'stream'; 2 | 3 | import { detect } from './CSV'; 4 | import { Parser } from './Parser'; 5 | import { Quote, Comma } from './types'; 6 | 7 | export class Streamer extends Transform { 8 | buffer: string; 9 | sep?: Comma; 10 | quo?: Quote; 11 | 12 | constructor(options?: { separator?: Comma; quote?: Quote }) { 13 | super({ 14 | readableObjectMode: true, 15 | writableObjectMode: false, 16 | }); 17 | // Transform.call(this, ); 18 | this.buffer = ''; 19 | this.sep = options && options.separator; 20 | this.quo = options && options.quote; 21 | } 22 | 23 | // overridden function with same signature 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 25 | _transform(chunk: any, _encoding: string, callback: TransformCallback): void { 26 | this.buffer = this.buffer.concat(chunk.toString()); 27 | if (this.sep === undefined) { 28 | // try to detect the separator if not provided 29 | this.sep = detect(this.buffer); 30 | } 31 | 32 | const csv = new Parser(this.buffer, this.sep, this.quo); 33 | const rows = csv.File(); 34 | 35 | if (csv.linePointer !== csv.pointer) { 36 | rows.pop(); 37 | } 38 | this.buffer = this.buffer.slice(csv.linePointer); 39 | if (rows.length > 0) { 40 | rows.forEach((row) => { 41 | this.push(row); 42 | }); 43 | } 44 | callback(); 45 | } 46 | 47 | // TODO 48 | /* 49 | push(chunk: any, encoding?: string | undefined): boolean { 50 | throw new Error("Method not implemented."); 51 | } 52 | */ 53 | 54 | _flush(callback: TransformCallback): void { 55 | const csv = new Parser(this.buffer, this.sep, this.quo); 56 | const rows = csv.File(); 57 | if (rows.length > 0) { 58 | rows.forEach((row) => { 59 | this.push(row); 60 | }); 61 | } 62 | callback(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/__tests__/csv.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | import * as CSV from '../'; 5 | import { Comma } from '../types'; 6 | 7 | describe('CSV', () => { 8 | describe('stringify()', () => { 9 | it('should #1', () => { 10 | const str = CSV.stringify([0, 1, 2, 3]); 11 | expect(str).toEqual('0,1,2,3\r\n'); 12 | }); 13 | it('should #2', () => { 14 | const str = CSV.stringify(['1', '2', '3']); 15 | expect(str).toEqual('1,2,3\r\n'); 16 | }); 17 | it('should #3', () => { 18 | const str = CSV.stringify(['"1"', '2', '3']); 19 | expect(str).toEqual('"""1""",2,3\r\n'); 20 | }); 21 | it('should #4', () => { 22 | const str = CSV.stringify(['1', '2', '3,4']); 23 | expect(str).toEqual('1,2,"3,4"\r\n'); 24 | }); 25 | it('should #5', () => { 26 | const str = CSV.stringify(); 27 | expect(str).toEqual('\r\n'); 28 | }); 29 | it('should #5bis', () => { 30 | const str = CSV.stringify([]); 31 | expect(str).toEqual('\r\n'); 32 | }); 33 | it('should #6', () => { 34 | const str = CSV.stringify('1234'); 35 | expect(str).toEqual('1234\r\n'); 36 | }); 37 | it('should #7', () => { 38 | const str = CSV.stringify(1234); 39 | expect(str).toEqual('1234\r\n'); 40 | }); 41 | it('should #8', () => { 42 | const str = CSV.stringify('12,34'); 43 | expect(str).toEqual('"12,34"\r\n'); 44 | }); 45 | it('should #9', () => { 46 | const str = CSV.stringify({ 47 | a: 1, 48 | b: 2, 49 | c: 3, 50 | d: 4, 51 | }); 52 | expect(str).toEqual('1,2,3,4\r\n'); 53 | }); 54 | it('should #10', () => { 55 | const str = CSV.stringify(['a', 'b\nb', 'c']); 56 | expect(str).toEqual('a,"b\nb",c\r\n'); 57 | }); 58 | it('should #11', () => { 59 | const str = CSV.stringify({ 60 | a: 1, 61 | b: null, 62 | }); 63 | expect(str).toEqual('1,\r\n'); 64 | }); 65 | it('should #12', () => { 66 | const str = CSV.stringify([ 67 | [1, 2], 68 | [3, 4], 69 | [5, 6], 70 | ]); 71 | expect(str).toEqual('1,2\r\n3,4\r\n5,6\r\n'); 72 | }); 73 | it('should #13', () => { 74 | const header = ['one', 'two', 'three']; 75 | const line1 = ['', 'l-two', 'l-three']; 76 | const headerCols = CSV.stringify(header).split(',').length; 77 | const line1Cols = CSV.stringify(line1).split(',').length; 78 | expect(headerCols).toEqual(line1Cols); 79 | }); 80 | it('should #14', () => { 81 | const str = CSV.stringify({ 82 | a: null, 83 | b: '', 84 | c: 1, 85 | }); 86 | expect(str).toEqual(',,1\r\n'); 87 | }); 88 | }); 89 | 90 | describe('fetch()', () => { 91 | it('should #1', () => { 92 | const obj = CSV.fetch('a,b,c'); 93 | expect(obj).toEqual(['a', 'b', 'c']); 94 | }); 95 | it('should #2', () => { 96 | const obj = CSV.fetch('a ,\tb , c\n'); 97 | expect(obj).toEqual(['a ', '\tb ', ' c']); 98 | }); 99 | it('should #3', () => { 100 | const obj = CSV.fetch('"a ","\tb "," c\n"'); 101 | expect(obj).toEqual(['a ', '\tb ', ' c\n']); 102 | }); 103 | it('should #3', () => { 104 | const obj = CSV.fetch('"a" , "b"\t, "c" \n'); 105 | expect(obj).toEqual(['a', 'b', 'c']); 106 | }); 107 | it('should #4', () => { 108 | const obj = CSV.fetch('a,b,c\nd,e'); 109 | expect(obj).toEqual(['a', 'b', 'c']); 110 | }); 111 | it('should', () => { 112 | const obj = CSV.fetch('a,b,"c\nd",e'); 113 | expect(obj).toEqual(['a', 'b', 'c\nd', 'e']); 114 | }); 115 | it('should #5', () => { 116 | const obj = CSV.fetch('a,b,"c\n'); 117 | expect(obj).toEqual(['a', 'b']); 118 | }); 119 | it('should #6', () => { 120 | const obj = CSV.fetch('"a,a","b,b","c"'); 121 | expect(obj).toEqual(['a,a', 'b,b', 'c']); 122 | }); 123 | it('should #7', () => { 124 | const obj = CSV.fetch('"a,a" , "b,b","c"'); 125 | expect(obj).toEqual(['a,a', 'b,b', 'c']); 126 | }); 127 | it('should #8', () => { 128 | const obj = CSV.fetch('"a,a",,"c"'); 129 | expect(obj).toEqual(['a,a', '', 'c']); 130 | }); 131 | it('should #9', () => { 132 | const obj = CSV.fetch("a'a;b;;c\nd,e,f,g", ';'); 133 | expect(obj).toEqual(["a'a", 'b', '', 'c']); 134 | }); 135 | it('should #10', () => { 136 | const obj = CSV.fetch('a,b,c\r\n"d","e","f"'); 137 | expect(obj).toEqual(['a', 'b', 'c']); 138 | }); 139 | it('should #11', () => { 140 | const obj = CSV.fetch('a,"b,b""b,b""b",c\r\nd,e,f'); 141 | expect(obj).toEqual(['a', 'b,b"b,b"b', 'c']); 142 | }); 143 | it('should #12', () => { 144 | const obj = CSV.fetch('a,"b1,""b2""b3,b4""""",c\r\nd,e,f'); 145 | expect(obj).toEqual(['a', 'b1,"b2"b3,b4""', 'c']); 146 | }); 147 | it('should #13', () => { 148 | const obj = CSV.fetch('a,"b1,""b2""b3,b4""""b5\r\nb6"\r\nc,d'); 149 | expect(obj).toEqual(['a', 'b1,"b2"b3,b4""b5\r\nb6']); 150 | }); 151 | it('should #14', () => { 152 | const obj = CSV.fetch('a,"b1,""b2""b3,b4""""b5\nb6" \t\r\nc,d'); 153 | expect(obj).toEqual(['a', 'b1,"b2"b3,b4""b5\nb6']); 154 | }); 155 | it('should #15', () => { 156 | const obj = CSV.fetch('a,"b1,""b2""b3,b4""""b5\nb6" ,c\r\n,d'); 157 | expect(obj).toEqual(['a', 'b1,"b2"b3,b4""b5\nb6', 'c']); 158 | }); 159 | it('should #15', () => { 160 | const obj = CSV.fetch('a,",b",c\r\n,d'); 161 | expect(obj).toEqual(['a', ',b', 'c']); 162 | }); 163 | it('should #16', () => { 164 | const obj = CSV.fetch('a,"b",c\r\na,"b""b",c'); 165 | expect(obj).toEqual(['a', 'b', 'c']); 166 | }); 167 | it('should #17', () => { 168 | const obj = CSV.fetch('a,this "should" work,b'); 169 | expect(obj).toEqual(['a', 'this "should" work', 'b']); 170 | }); 171 | }); 172 | 173 | describe('forEach()', () => { 174 | it('should #1', () => { 175 | let i = 0; 176 | CSV.forEach('a,b,c\nd,e,f\ng,h,i', (row, index) => { 177 | expect(index).toEqual(i++); 178 | if (index === 0) { 179 | expect(row).toEqual(['a', 'b', 'c']); 180 | } else if (index == 1) { 181 | expect(row).toEqual(['d', 'e', 'f']); 182 | } else if (index == 2) { 183 | expect(row).toEqual(['g', 'h', 'i']); 184 | } 185 | }); 186 | }); 187 | it('should #2', () => { 188 | let i = 0; 189 | CSV.forEach('a,b,c\nd,e,f\ng,h,i', ',', (row, index) => { 190 | expect(index).toEqual(i++); 191 | if (index == 0) { 192 | expect(row).toEqual(['a', 'b', 'c']); 193 | } else if (index == 1) { 194 | expect(row).toEqual(['d', 'e', 'f']); 195 | } else if (index == 2) { 196 | expect(row).toEqual(['g', 'h', 'i']); 197 | } 198 | }); 199 | }); 200 | it('should #3', () => { 201 | let i = 0; 202 | CSV.forEach('a,b,c\nd,e,f\ng,h', ',', (row, index) => { 203 | expect(index).toEqual(i++); 204 | if (index == 0) { 205 | expect(row).toEqual(['a', 'b', 'c']); 206 | } else if (index == 1) { 207 | expect(row).toEqual(['d', 'e', 'f']); 208 | } else if (index == 2) { 209 | expect(row).toEqual(['g', 'h']); 210 | } 211 | }); 212 | }); 213 | }); 214 | 215 | describe('parse()', () => { 216 | it('should #1', () => { 217 | const obj = CSV.parse('a,b,c\nd,e,f\ng,h,i'); 218 | expect(obj[0]).toEqual(['a', 'b', 'c']); 219 | expect(obj[1]).toEqual(['d', 'e', 'f']); 220 | expect(obj[2]).toEqual(['g', 'h', 'i']); 221 | }); 222 | it('should #2', () => { 223 | const data = 'a,b,c,1,"hello ""world""",12,14'; 224 | const cols = CSV.parse(data)[0]; 225 | expect(cols.length).toEqual(7); 226 | const expected = ['a', 'b', 'c', '1', 'hello "world"', '12', '14']; 227 | expect(JSON.stringify(cols)).toEqual(JSON.stringify(expected)); 228 | }); 229 | it('should #3', () => { 230 | const data = 'a,b,c,1,"hello, ""world""",12,14'; 231 | const cols = CSV.parse(data)[0]; 232 | expect(cols.length).toEqual(7); 233 | const expected = ['a', 'b', 'c', '1', 'hello, "world"', '12', '14']; 234 | expect(JSON.stringify(cols)).toEqual(JSON.stringify(expected)); 235 | }); 236 | it('should #4', () => { 237 | const obj = CSV.parse('a;b;c\nd;e;f\ng;h;i'); 238 | expect(obj[0]).toEqual(['a', 'b', 'c']); 239 | expect(obj[1]).toEqual(['d', 'e', 'f']); 240 | }); 241 | it('should #5', () => { 242 | const obj = CSV.parse('a,b,c\nd,e,f\ng,h,i', ',,,,,,' as Comma); 243 | expect(obj[0]).toEqual(['a', 'b', 'c']); 244 | expect(obj[1]).toEqual(['d', 'e', 'f']); 245 | expect(obj[2]).toEqual(['g', 'h', 'i']); 246 | }); 247 | it('should #6', () => { 248 | const obj = CSV.parse('a,b,c\nd,e,f\ng,h,i', ','); 249 | expect(obj[0]).toEqual(['a', 'b', 'c']); 250 | expect(obj[1]).toEqual(['d', 'e', 'f']); 251 | expect(obj[2]).toEqual(['g', 'h', 'i']); 252 | }); 253 | it('should #7', () => { 254 | const obj = CSV.parse('a,b,c\nd,e,f\ng,h,i', { output: "tuples" }); 255 | expect(obj[0]).toEqual(['a', 'b', 'c']); 256 | expect(obj[1]).toEqual(['d', 'e', 'f']); 257 | expect(obj[2]).toEqual(['g', 'h', 'i']); 258 | }); 259 | it('should #8', () => { 260 | const obj = CSV.parse('a,b,c\n1,2,3\n4,5,6\n7,8,9', { output: "objects" }); 261 | expect(obj).toHaveLength(3); 262 | expect(obj[0]).toMatchObject({ a: '1', b: '2', c: '3' }); 263 | expect(obj[1]).toMatchObject({ a: '4', b: '5', c: '6' }); 264 | expect(obj[2]).toMatchObject({ a: '7', b: '8', c: '9' }); 265 | }); 266 | it('should #9', () => { 267 | const obj = CSV.parse('a,b,c', { output: "objects" }); 268 | expect(obj).toHaveLength(0); 269 | }); 270 | it('should #10', () => { 271 | const obj = CSV.parse('', { output: "objects" }); 272 | expect(obj).toHaveLength(0); 273 | }); 274 | it('should #11', () => { 275 | const obj = CSV.parse('a, b, c\n1, 2, 3\n4, 5, 6', { output: "objects" }); 276 | expect(obj).toMatchObject([ 277 | { a: "1", b: " 2", c: " 3" }, 278 | { a: "4", b: " 5", c: " 6" }, 279 | ]); 280 | }); 281 | it('should #12', () => { 282 | const obj = CSV.parse('a, b, c\n1, 2, 3\n4, 5, 6', { output: "tuples" }); 283 | expect(obj).toMatchObject([ 284 | [ "a", " b", " c" ], 285 | [ "1", " 2", " 3" ], 286 | [ "4", " 5", " 6" ], 287 | ]); 288 | }); 289 | }); 290 | 291 | describe('parse+stringify', () => { 292 | it('should #1', () => { 293 | const testdata = [ 294 | ['a\rb', 'cd'], 295 | ['a\r,\rb', 'cd'], 296 | ]; 297 | expect(testdata).toEqual(CSV.parse(CSV.stringify(testdata))); 298 | }); 299 | // TODO 300 | it.skip('should #2', () => { 301 | const testdata = 'abcd\rb,cd\r\n'; 302 | console.log( 303 | JSON.stringify(testdata), 304 | JSON.stringify(CSV.parse(testdata)), 305 | JSON.stringify(CSV.stringify(CSV.parse(testdata))) 306 | ); 307 | expect(testdata).toEqual(CSV.stringify(CSV.parse(testdata))); 308 | }); 309 | }); 310 | 311 | describe('detect()', () => { 312 | it('should #1', () => { 313 | expect(CSV.detect('a,b,c\nd,e,f\ng,h,i')).toEqual(','); 314 | expect(CSV.detect('a;b;c\nd;e;f\ng;h;i')).toEqual(';'); 315 | expect(CSV.detect('a|b|c\nd|e|f\ng|h|i')).toEqual('|'); 316 | expect(CSV.detect('a\tb\tc\nd\te\tf\ng\th\ti')).toEqual('\t'); 317 | }); 318 | }); 319 | 320 | describe('readAll()', () => { 321 | it('should #0', () => { 322 | CSV.readAll('A,B,C\nD,E,F', (rows) => { 323 | expect(rows).toContainEqual(['A', 'B', 'C']); 324 | expect(rows).toContainEqual(['D', 'E', 'F']); 325 | }); 326 | }); 327 | it('should #1', () => { 328 | CSV.readAll('A,B,C\nD,E,F\n', (rows) => { 329 | expect(rows).toContainEqual(['A', 'B', 'C']); 330 | expect(rows).toContainEqual(['D', 'E', 'F']); 331 | }); 332 | }); 333 | it('should #2', () => { 334 | CSV.readAll('A,B,C\nD,E,"F\n', (rows) => { 335 | expect(rows).toContainEqual(['A', 'B', 'C']); 336 | expect(rows).not.toContainEqual(['D', 'E', 'F']); 337 | }); 338 | }); 339 | it('should #3', () => { 340 | CSV.readAll('A,B,C\nD,E', (rows) => { 341 | expect(rows).toContainEqual(['A', 'B', 'C']); 342 | expect(rows).toContainEqual(['D', 'E']); 343 | }); 344 | }); 345 | }); 346 | 347 | describe('readChunk()', () => { 348 | it('should #1', () => { 349 | CSV.readChunk('A,B,C\nD,E,F', (rows) => { 350 | expect(rows).toContainEqual(['A', 'B', 'C']); 351 | expect(rows).not.toContainEqual(['D', 'E', 'F']); 352 | }); 353 | }); 354 | it('should #2', () => { 355 | CSV.readChunk('A,B,C\nD,E,"F\n', (rows) => { 356 | expect(rows).toContainEqual(['A', 'B', 'C']); 357 | expect(rows).not.toContainEqual(['D', 'E', 'F']); 358 | }); 359 | }); 360 | it('should #3', () => { 361 | CSV.readChunk('A,B,C\nD,E', (rows) => { 362 | expect(rows).toContainEqual(['A', 'B', 'C']); 363 | expect(rows).not.toContainEqual(['D', 'E']); 364 | }); 365 | }); 366 | }); 367 | 368 | describe('readStream()', () => { 369 | it('should #1', (done) => { 370 | const reader = fs.createReadStream( 371 | path.resolve(__dirname, './sample.csv') 372 | ); 373 | const parser = CSV.createStream({ separator: ',' }); 374 | const rows: string[] = []; 375 | parser.on('data', (row) => { 376 | rows.push(row); 377 | }); 378 | parser.on('end', () => { 379 | expect(rows).toEqual(CSV.parse('1,2,"3,3",4\n1,2,3,4')); 380 | done(); 381 | }); 382 | reader.pipe(parser); 383 | }); 384 | it('should #2', (done) => { 385 | const reader = fs.createReadStream( 386 | path.resolve(__dirname, './sample.csv'), 387 | { highWaterMark: 5 } 388 | ); 389 | const parser = CSV.createStream({ separator: ',' }); 390 | const rows: string[] = []; 391 | parser.on('data', (row) => { 392 | rows.push(row); 393 | }); 394 | parser.on('end', () => { 395 | expect(rows).toEqual(CSV.parse('1,2,"3,3",4\n1,2,3,4')); 396 | done(); 397 | }); 398 | reader.pipe(parser); 399 | }); 400 | }); 401 | }); 402 | -------------------------------------------------------------------------------- /src/__tests__/sample.csv: -------------------------------------------------------------------------------- 1 | 1,2,"3,3",4 2 | 1,2,3,4 3 | -------------------------------------------------------------------------------- /src/__tests__/sample.txt: -------------------------------------------------------------------------------- 1 | 1|2|#3|3#|4 2 | 1|2|3|4 -------------------------------------------------------------------------------- /src/__tests__/tsv.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | import * as CSV from '../'; 5 | 6 | describe('TSV', () => { 7 | describe('fetch()', () => { 8 | it('should #1', () => { 9 | const obj = CSV.fetch('a\tb\tc', '\t'); 10 | expect(obj).toEqual(['a', 'b', 'c']); 11 | }); 12 | it('should #1a', () => { 13 | const obj = CSV.fetch('a \t,b \t c\n', '\t'); 14 | expect(obj).toEqual(['a ', ',b ', ' c']); 15 | }); 16 | it('should #1b', () => { 17 | const obj = CSV.fetch('"a "\t"\tb "\t" c\n"', '\t'); 18 | expect(obj).toEqual(['a ', '\tb ', ' c\n']); 19 | }); 20 | it('should #1c', () => { 21 | const obj = CSV.fetch('"a" \t "b" \t "c" \n', '\t'); 22 | expect(obj).toEqual(['a', 'b', 'c']); 23 | }); 24 | it('should #2', () => { 25 | const obj = CSV.fetch('a\tb\tc\nd\te', '\t'); 26 | expect(obj).toEqual(['a', 'b', 'c']); 27 | }); 28 | it('should #3', () => { 29 | const obj = CSV.fetch('a\tb\t"c\nd"\te', '\t'); 30 | expect(obj).toEqual(['a', 'b', 'c\nd', 'e']); 31 | }); 32 | it('should #4', () => { 33 | const obj = CSV.fetch('a\tb\t"c\n', '\t'); 34 | expect(obj).toEqual(['a', 'b']); 35 | }); 36 | it('should #5', () => { 37 | const obj = CSV.fetch('"a\ta"\t"b\tb"\t"c"', '\t'); 38 | expect(obj).toEqual(['a\ta', 'b\tb', 'c']); 39 | }); 40 | it('should #5b', () => { 41 | const obj = CSV.fetch('"a\ta" \t "b\tb"\t"c"', '\t'); 42 | expect(obj).toEqual(['a\ta', 'b\tb', 'c']); 43 | }); 44 | it('should #5c', () => { 45 | const obj = CSV.fetch('"a\ta"\t\t"c"', '\t'); 46 | expect(obj).toEqual(['a\ta', '', 'c']); 47 | }); 48 | it('should #6', () => { 49 | const obj = CSV.fetch("a'a;b;;c\nd\te\tf\tg", ';'); 50 | expect(obj).toEqual(["a'a", 'b', '', 'c']); 51 | }); 52 | it('should #7', () => { 53 | const obj = CSV.fetch('a\tb\tc\r\n"d"\t"e"\t"f"', '\t'); 54 | expect(obj).toEqual(['a', 'b', 'c']); 55 | }); 56 | it('should #8', () => { 57 | const obj = CSV.fetch('a\t"b\tb""b\tb""b"\tc\r\nd\te\tf', '\t'); 58 | expect(obj).toEqual(['a', 'b\tb"b\tb"b', 'c']); 59 | }); 60 | it('should #9', () => { 61 | const obj = CSV.fetch('a\t"b1\t""b2""b3\tb4"""""\tc\r\nd\te\tf', '\t'); 62 | expect(obj).toEqual(['a', 'b1\t"b2"b3\tb4""', 'c']); 63 | }); 64 | it('should #10', () => { 65 | const obj = CSV.fetch('a\t"b1\t""b2""b3\tb4""""b5\r\nb6"\r\nc\td', '\t'); 66 | expect(obj).toEqual(['a', 'b1\t"b2"b3\tb4""b5\r\nb6']); 67 | }); 68 | it('should #10bis', () => { 69 | const obj = CSV.fetch('a\t"b1\t""b2""b3\tb4""""b5\nb6" \r\nc\td', '\t'); 70 | expect(obj).toEqual(['a', 'b1\t"b2"b3\tb4""b5\nb6']); 71 | }); 72 | it('should #10ter', () => { 73 | const obj = CSV.fetch('a\t"b1\t""b2""b3\tb4""""b5\nb6" \tc\r\n\td', '\t'); 74 | expect(obj).toEqual(['a', 'b1\t"b2"b3\tb4""b5\nb6', 'c']); 75 | }); 76 | it('should #11', () => { 77 | const obj = CSV.fetch('a\t"\tb"\tc\r\n\td', '\t'); 78 | expect(obj).toEqual(['a', '\tb', 'c']); 79 | }); 80 | it('should #12', () => { 81 | const obj = CSV.fetch('a\t"b"\tc\r\na\t"b""b"\tc', '\t'); 82 | expect(obj).toEqual(['a', 'b', 'c']); 83 | }); 84 | it('should get simple fields containing double quotes #13', () => { 85 | const obj = CSV.fetch('a\tthis "should" work\tb', '\t'); 86 | expect(obj).toEqual(['a', 'this "should" work', 'b']); 87 | }); 88 | it('should get simple fields containing doubled simple quotes #14', () => { 89 | const obj = CSV.fetch("a\tb''b\tc", '\t'); 90 | expect(obj).toEqual(['a', "b''b", 'c']); 91 | }); 92 | }); 93 | describe('fetch() with alternative quote and separator', () => { 94 | it('should #1', () => { 95 | const obj = CSV.fetch('a|b|c', '|', '#'); 96 | expect(obj).toEqual(['a', 'b', 'c']); 97 | }); 98 | it('should #1a', () => { 99 | const obj = CSV.fetch('a |,b | c\n', '|', '#'); 100 | expect(obj).toEqual(['a ', ',b ', ' c']); 101 | }); 102 | it('should #1b', () => { 103 | const obj = CSV.fetch('#a #|#|b #|# c\n#', '|', '#'); 104 | expect(obj).toEqual(['a ', '|b ', ' c\n']); 105 | }); 106 | it('should #1c', () => { 107 | const obj = CSV.fetch('#a# | #b# | #c# \n', '|', '#'); 108 | expect(obj).toEqual(['a', 'b', 'c']); 109 | }); 110 | it('should #2', () => { 111 | const obj = CSV.fetch('a|b|c\nd|e', '|', '#'); 112 | expect(obj).toEqual(['a', 'b', 'c']); 113 | }); 114 | it('should #3', () => { 115 | const obj = CSV.fetch('a|b|#c\nd#|e', '|', '#'); 116 | expect(obj).toEqual(['a', 'b', 'c\nd', 'e']); 117 | }); 118 | it('should #4', () => { 119 | const obj = CSV.fetch('a|b|#c\n', '|', '#'); 120 | expect(obj).toEqual(['a', 'b']); 121 | }); 122 | it('should #5', () => { 123 | const obj = CSV.fetch('#a|a#|#b|b#|#c#', '|', '#'); 124 | expect(obj).toEqual(['a|a', 'b|b', 'c']); 125 | }); 126 | it('should #5b', () => { 127 | const obj = CSV.fetch('#a|a# | #b|b#|#c#', '|', '#'); 128 | expect(obj).toEqual(['a|a', 'b|b', 'c']); 129 | }); 130 | it('should #5c', () => { 131 | const obj = CSV.fetch('#a|a#||#c#', '|', '#'); 132 | expect(obj).toEqual(['a|a', '', 'c']); 133 | }); 134 | it('should #6', () => { 135 | const obj = CSV.fetch("a'a;b;;c\nd|e|f|g", ';'); 136 | expect(obj).toEqual(["a'a", 'b', '', 'c']); 137 | }); 138 | it('should #7', () => { 139 | const obj = CSV.fetch('a|b|c\r\n#d#|#e#|#f#', '|', '#'); 140 | expect(obj).toEqual(['a', 'b', 'c']); 141 | }); 142 | it('should #8', () => { 143 | const obj = CSV.fetch('a|#b|b##b|b##b#|c\r\nd|e|f', '|', '#'); 144 | expect(obj).toEqual(['a', 'b|b#b|b#b', 'c']); 145 | }); 146 | it('should #9', () => { 147 | const obj = CSV.fetch('a|#b1|##b2##b3|b4#####|c\r\nd|e|f', '|', '#'); 148 | expect(obj).toEqual(['a', 'b1|#b2#b3|b4##', 'c']); 149 | }); 150 | it('should #12', () => { 151 | const obj = CSV.fetch('a|#b#|c\r\na|#b##b#|c', '|', '#'); 152 | expect(obj).toEqual(['a', 'b', 'c']); 153 | }); 154 | it('should get simple fields containing double quotes #13', () => { 155 | const obj = CSV.fetch('a|this #should# work|b', '|', '#'); 156 | expect(obj).toEqual(['a', 'this #should# work', 'b']); 157 | }); 158 | it('should get simple fields containing doubled simple quotes #14', () => { 159 | const obj = CSV.fetch("a|b''b|c", '|', '#'); 160 | expect(obj).toEqual(['a', "b''b", 'c']); 161 | }); 162 | }); 163 | 164 | describe('fetch() with separator and no quote', () => { 165 | it('should #1', () => { 166 | const obj = CSV.fetch('a\tb\tc', '\t', '\b'); 167 | expect(obj).toEqual(['a', 'b', 'c']); 168 | }); 169 | it('should #2', () => { 170 | const obj = CSV.fetch('a\t"b"\tc', '\t', '\b'); 171 | expect(obj).toEqual(['a', '"b"', 'c']); 172 | }); 173 | it('should #3', () => { 174 | const obj = CSV.fetch('a\t"b"b\tc', '\t', '\b'); 175 | expect(obj).toEqual(['a', '"b"b', 'c']); 176 | }); 177 | }); 178 | 179 | describe('readStream()', () => { 180 | it('should #1', () => { 181 | const reader = fs.createReadStream( 182 | path.resolve(__dirname, './sample.txt') 183 | ); 184 | const parser = CSV.createStream({ separator: '|', quote: '#' }); 185 | const rows: string[] = []; 186 | parser.on('data', (row) => { 187 | rows.push(row); 188 | }); 189 | parser.on('end', () => { 190 | expect(rows).toEqual(CSV.parse('1|2|#3|3#|4\n1|2|3|4', '|', '#')); 191 | }); 192 | reader.pipe(parser); 193 | }); 194 | it('should #2', () => { 195 | const reader = fs.createReadStream( 196 | path.resolve(__dirname, './sample.txt'), 197 | { highWaterMark: 5 } 198 | ); 199 | const parser = CSV.createStream({ separator: '|', quote: '#' }); 200 | const rows: string[] = []; 201 | parser.on('data', (row) => { 202 | rows.push(row); 203 | }); 204 | parser.on('end', () => { 205 | // should.deepEqual(rows, CSV.parse("1|2|#3|3#|4\n1|2|3|4", "|", "#")); 206 | expect(rows).toEqual(CSV.parse('1|2|#3|3#|4\n1|2|3|4', '|', '#')); 207 | }); 208 | reader.pipe(parser); 209 | }); 210 | }); 211 | 212 | /* */ 213 | }); 214 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CSV'; 2 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type LineBreak = '\r\n' | '\n' | '\r'; 2 | export type Comma = ',' | ';' | '|' | '\t'; 3 | export type Quote = '"' | string; 4 | export type Residue = ' ' | '\t' | '\f' | string; 5 | export type Value = string; 6 | export type PristineInput = 7 | | string 8 | | number 9 | | null 10 | | undefined 11 | | { [key: string]: PristineInput } 12 | | PristineInput[]; 13 | export type ReadCallback = (row: Value[]) => void; 14 | export type ReadAllCallback = (rows: Value[][]) => void; 15 | export type ForEachCallback = (row: Value[], index: number) => void; 16 | export type ParseOptions = { 17 | comma: Comma; 18 | quote: Quote; 19 | output: 'objects' | 'tuples'; 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "strict": true, 8 | "types": ["node", "jest"], 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true 11 | }, 12 | "include": ["./src"], 13 | "exclude": ["**/__tests__/"] 14 | } 15 | --------------------------------------------------------------------------------