| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | /* | |
| 3 | ||
| 4 | Node CSV | |
| 5 | ======== | |
| 6 | ||
| 7 | This project provides CSV parsing and has been tested and used | |
| 8 | on a large input file (over 2Gb). | |
| 9 | ||
| 10 | * Follow the NodeJs streaming API | |
| 11 | * Async and event based | |
| 12 | * Support delimiters, quotes and escape characters | |
| 13 | * Line breaks discovery: detected in source and reported to destination | |
| 14 | * Data transformation | |
| 15 | * Support for large datasets | |
| 16 | * Complete test coverage as sample and inspiration | |
| 17 | * no external dependencies | |
| 18 | ||
| 19 | Important, this documentation cover the current version of the node | |
| 20 | csv parser. The documentation for the current version 0.1.0 is | |
| 21 | available [here](https://github.com/wdavidw/node-csv-parser/tree/v0.1). | |
| 22 | ||
| 23 | Quick example | |
| 24 | ------------- | |
| 25 | ||
| 26 | // node samples/string.js | |
| 27 | var csv = require('csv'); | |
| 28 | csv() | |
| 29 | .from( '"1","2","3","4"\n"a","b","c","d"' ) | |
| 30 | .to( console.log ) | |
| 31 | // Output: | |
| 32 | // 1,2,3,4 | |
| 33 | // a,b,c,d | |
| 34 | ||
| 35 | Advanced example | |
| 36 | ---------------- | |
| 37 | ||
| 38 | The following example illustrates 4 usages of the library: | |
| 39 | 1. Plug a readable stream by defining a file path | |
| 40 | 2. Direct output to a file path | |
| 41 | 3. Transform the data (optional) | |
| 42 | 4. Listen to events (optional) | |
| 43 | ||
| 44 | // node samples/sample.js | |
| 45 | var csv = require('csv'); | |
| 46 | csv() | |
| 47 | .from.stream(fs.createReadStream(__dirname+'/sample.in') | |
| 48 | .to.path(__dirname+'/sample.out') | |
| 49 | .transform( function(data){ | |
| 50 | data.unshift(data.pop()); | |
| 51 | return data; | |
| 52 | }) | |
| 53 | .on('record', function(data,index){ | |
| 54 | console.log('#'+index+' '+JSON.stringify(data)); | |
| 55 | }) | |
| 56 | .on('end', function(count){ | |
| 57 | console.log('Number of lines: '+count); | |
| 58 | }) | |
| 59 | .on('error', function(error){ | |
| 60 | console.log(error.message); | |
| 61 | }); | |
| 62 | // Output: | |
| 63 | // #0 ["2000-01-01","20322051544","1979.0","8.8017226E7","ABC","45"] | |
| 64 | // #1 ["2050-11-27","28392898392","1974.0","8.8392926E7","DEF","23"] | |
| 65 | // Number of lines: 2 | |
| 66 | ||
| 67 | Pipe example | |
| 68 | ------------ | |
| 69 | ||
| 70 | The module follow a Stream architecture. At it's core, the parser and | |
| 71 | the stringifier utilities provide a [Stream Writer][writable_stream] | |
| 72 | and a [Stream Reader][readable_stream] implementation available in the CSV API. | |
| 73 | ||
| 74 | |-----------| |---------|---------| |---------| | |
| 75 | | | | | | | | | |
| 76 | | | | CSV | | | | |
| 77 | | | | | | | | | |
| 78 | | Stream | | Writer | Reader | | Stream | | |
| 79 | | Reader |.pipe(| API | API |).pipe(| Writer |) | |
| 80 | | | | | | | | | |
| 81 | | | | | | | | | |
| 82 | |-----------| |---------|---------| |---------| | |
| 83 | ||
| 84 | Here's a quick example: | |
| 85 | ||
| 86 | in = fs.createReadStream('./in') | |
| 87 | out = fs.createWriteStream('./out') | |
| 88 | in.pipe(csv()).pipe(out) | |
| 89 | ||
| 90 | Installing | |
| 91 | ---------- | |
| 92 | ||
| 93 | Via [npm](http://github.com/isaacs/npm): | |
| 94 | ```bash | |
| 95 | npm install csv | |
| 96 | ``` | |
| 97 | ||
| 98 | Via git (or downloaded tarball): | |
| 99 | ```bash | |
| 100 | git clone http://github.com/wdavidw/node-csv-parser.git | |
| 101 | ``` | |
| 102 | ||
| 103 | Events | |
| 104 | ------ | |
| 105 | ||
| 106 | The library extends Node [EventEmitter][event] class and emit all | |
| 107 | the events of the Writable and Readable [Stream API][stream]. Additionally, the useful "records" event | |
| 108 | is emitted. | |
| 109 | ||
| 110 | * *record* | |
| 111 | Emitted by the stringifier when a new row is parsed and transformed. The data is | |
| 112 | the value returned by the user `transform` callback if any. Note however that the event won't | |
| 113 | be called if transform return `null` since the record is skipped. | |
| 114 | The callback provides two arguments. `data` is the CSV line being processed (an array or an object) | |
| 115 | and `index` is the index number of the line starting at zero | |
| 116 | * *data* | |
| 117 | Emitted by the stringifier on each line once the data has been transformed and stringified. | |
| 118 | * *drain* | |
| 119 | * *end* | |
| 120 | Emitted when the CSV content has been parsed. | |
| 121 | * *close* | |
| 122 | Emitted when the underlying resource has been closed. For example, when writting to a file with `csv().to.path()`, the event will be called once the writing process is complete and the file closed. | |
| 123 | * *error* | |
| 124 | Thrown whenever an error occured. | |
| 125 | */ | |
| 126 | ||
| 127 | 1 | var CSV, from, options, parser, state, stream, stringifier, to, transformer; |
| 128 | ||
| 129 | 1 | stream = require('stream'); |
| 130 | ||
| 131 | 1 | state = require('./state'); |
| 132 | ||
| 133 | 1 | options = require('./options'); |
| 134 | ||
| 135 | 1 | from = require('./from'); |
| 136 | ||
| 137 | 1 | to = require('./to'); |
| 138 | ||
| 139 | 1 | stringifier = require('./stringifier'); |
| 140 | ||
| 141 | 1 | parser = require('./parser'); |
| 142 | ||
| 143 | 1 | transformer = require('./transformer'); |
| 144 | ||
| 145 | 1 | CSV = function() { |
| 146 | 78 | this.paused = false; |
| 147 | 78 | this.readable = true; |
| 148 | 78 | this.writable = true; |
| 149 | 78 | this.state = state(); |
| 150 | 78 | this.options = options(); |
| 151 | 78 | this.from = from(this); |
| 152 | 78 | this.to = to(this); |
| 153 | 78 | this.parser = parser(this); |
| 154 | 78 | this.parser.on('row', (function(row) { |
| 155 | 27450 | return this.transformer.transform(row); |
| 156 | }).bind(this)); | |
| 157 | 78 | this.parser.on('end', (function() { |
| 158 | 73 | return this.transformer.end(); |
| 159 | }).bind(this)); | |
| 160 | 78 | this.parser.on('error', (function(e) { |
| 161 | 3 | return this.error(e); |
| 162 | }).bind(this)); | |
| 163 | 78 | this.stringifier = stringifier(this); |
| 164 | 78 | this.transformer = transformer(this); |
| 165 | 78 | this.transformer.on('end', (function() { |
| 166 | 73 | return this.emit('end', this.state.count); |
| 167 | }).bind(this)); | |
| 168 | 78 | return this; |
| 169 | }; | |
| 170 | ||
| 171 | 1 | CSV.prototype.__proto__ = stream.prototype; |
| 172 | ||
| 173 | /* | |
| 174 | ||
| 175 | `pause()` | |
| 176 | --------- | |
| 177 | ||
| 178 | Implementation of the Readable Stream API, requesting that no further data | |
| 179 | be sent until resume() is called. | |
| 180 | */ | |
| 181 | ||
| 182 | ||
| 183 | 1 | CSV.prototype.pause = function() { |
| 184 | 19463 | return this.paused = true; |
| 185 | }; | |
| 186 | ||
| 187 | /* | |
| 188 | ||
| 189 | `resume()` | |
| 190 | ---------- | |
| 191 | ||
| 192 | Implementation of the Readable Stream API, resuming the incoming 'data' | |
| 193 | events after a pause(). | |
| 194 | */ | |
| 195 | ||
| 196 | ||
| 197 | 1 | CSV.prototype.resume = function() { |
| 198 | 15346 | this.paused = false; |
| 199 | 15346 | return this.emit('drain'); |
| 200 | }; | |
| 201 | ||
| 202 | /* | |
| 203 | ||
| 204 | `write(data, [preserve])` | |
| 205 | ------------------------- | |
| 206 | ||
| 207 | Implementation of the Writable Stream API with a larger signature. Data | |
| 208 | may be a string, a buffer, an array or an object. | |
| 209 | ||
| 210 | If data is a string or a buffer, it could span multiple lines. If data | |
| 211 | is an object or an array, it must represent a single line. | |
| 212 | Preserve is for line which are not considered as CSV data. | |
| 213 | */ | |
| 214 | ||
| 215 | ||
| 216 | 1 | CSV.prototype.write = function(data, preserve) { |
| 217 | 29461 | var csv; |
| 218 | 29461 | if (!this.writable) { |
| 219 | 0 | return false; |
| 220 | } | |
| 221 | 29461 | if (data instanceof Buffer) { |
| 222 | 26286 | data = data.toString(); |
| 223 | } | |
| 224 | 29461 | if (typeof data === 'string' && !preserve) { |
| 225 | 26420 | this.parser.parse(data); |
| 226 | 3041 | } else if (Array.isArray(data) && !this.state.transforming) { |
| 227 | 2020 | csv = this; |
| 228 | 2020 | this.transformer.transform(data); |
| 229 | } else { | |
| 230 | 1021 | if (preserve || this.state.transforming) { |
| 231 | 9 | this.stringifier.write(data, preserve); |
| 232 | } else { | |
| 233 | 1012 | this.transformer.transform(data); |
| 234 | } | |
| 235 | } | |
| 236 | 29461 | return !this.paused; |
| 237 | }; | |
| 238 | ||
| 239 | /* | |
| 240 | ||
| 241 | `end()` | |
| 242 | ------- | |
| 243 | ||
| 244 | Terminate the parsing. Call this method when no more csv data is | |
| 245 | to be parsed. It implement the StreamWriter API by setting the `writable` | |
| 246 | property to "false" and emitting the `end` event. | |
| 247 | */ | |
| 248 | ||
| 249 | ||
| 250 | 1 | CSV.prototype.end = function() { |
| 251 | 75 | if (!this.writable) { |
| 252 | 1 | return; |
| 253 | } | |
| 254 | 74 | this.readable = false; |
| 255 | 74 | this.writable = false; |
| 256 | 74 | return this.parser.end(); |
| 257 | }; | |
| 258 | ||
| 259 | /* | |
| 260 | ||
| 261 | `transform(callback)` | |
| 262 | --------------------- | |
| 263 | ||
| 264 | Register the transformer callback. The callback is a user provided | |
| 265 | function call on each line to filter, enrich or modify the | |
| 266 | dataset. More information in the "transforming data" section. | |
| 267 | */ | |
| 268 | ||
| 269 | ||
| 270 | 1 | CSV.prototype.transform = function(callback) { |
| 271 | 31 | this.transformer.callback = callback; |
| 272 | 31 | return this; |
| 273 | }; | |
| 274 | ||
| 275 | /* | |
| 276 | ||
| 277 | `error(error)` | |
| 278 | -------------- | |
| 279 | ||
| 280 | Unified mechanism to handle error, emit the error and mark the | |
| 281 | stream as non readable and non writable. | |
| 282 | */ | |
| 283 | ||
| 284 | ||
| 285 | 1 | CSV.prototype.error = function(e) { |
| 286 | 5 | this.readable = false; |
| 287 | 5 | this.writable = false; |
| 288 | 5 | this.emit('error', e); |
| 289 | 5 | if (this.readStream) { |
| 290 | 0 | this.readStream.destroy(); |
| 291 | } | |
| 292 | 5 | return this; |
| 293 | }; | |
| 294 | ||
| 295 | 1 | module.exports = function() { |
| 296 | 78 | return new CSV; |
| 297 | }; | |
| 298 | ||
| 299 | /* | |
| 300 | [event]: http://nodejs.org/api/events.html | |
| 301 | [stream]: http://nodejs.org/api/stream.html | |
| 302 | [writable_stream]: http://nodejs.org/api/stream.html#stream_writable_stream | |
| 303 | [readable_stream]: http://nodejs.org/api/stream.html#stream_readable_stream | |
| 304 | */ | |
| 305 |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | ||
| 3 | 1 | module.exports = function() { |
| 4 | 78 | return { |
| 5 | count: 0, | |
| 6 | field: '', | |
| 7 | line: [], | |
| 8 | lastC: '', | |
| 9 | countWriten: 0, | |
| 10 | transforming: 0 | |
| 11 | }; | |
| 12 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | /* | |
| 3 | Input and output options | |
| 4 | ======================== | |
| 5 | ||
| 6 | The `options` property provide access to the `from` and `to` object used to store options. This | |
| 7 | property is for internal usage and could be considered private. It is recommanded to use | |
| 8 | the `from.options()` and `to.options()` to access those objects. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | module.exports = function() { |
| 12 | 78 | return { |
| 13 | from: { | |
| 14 | delimiter: ',', | |
| 15 | quote: '"', | |
| 16 | escape: '"', | |
| 17 | columns: null, | |
| 18 | flags: 'r', | |
| 19 | encoding: 'utf8', | |
| 20 | trim: false, | |
| 21 | ltrim: false, | |
| 22 | rtrim: false | |
| 23 | }, | |
| 24 | to: { | |
| 25 | delimiter: null, | |
| 26 | quote: null, | |
| 27 | quoted: false, | |
| 28 | escape: null, | |
| 29 | columns: null, | |
| 30 | header: false, | |
| 31 | lineBreaks: null, | |
| 32 | flags: 'w', | |
| 33 | encoding: 'utf8', | |
| 34 | newColumns: false, | |
| 35 | end: true | |
| 36 | } | |
| 37 | }; | |
| 38 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | 1 | var Stream, fs, path, utils, _ref; |
| 3 | ||
| 4 | 1 | fs = require('fs'); |
| 5 | ||
| 6 | 1 | path = require('path'); |
| 7 | ||
| 8 | 1 | if ((_ref = fs.exists) == null) { |
| 9 | 0 | fs.exists = path.exists; |
| 10 | } | |
| 11 | ||
| 12 | 1 | utils = require('./utils'); |
| 13 | ||
| 14 | 1 | Stream = require('stream'); |
| 15 | ||
| 16 | /* | |
| 17 | ||
| 18 | Reading data from a source | |
| 19 | ========================== | |
| 20 | ||
| 21 | The `csv().from` property provides functions to read from an external | |
| 22 | source and write to a CSV instance. The source may be a string, a file, | |
| 23 | a buffer or a readable stream. | |
| 24 | ||
| 25 | You may call the `from` function or one of its sub function. For example, | |
| 26 | here are two identical ways to read from a file: | |
| 27 | ||
| 28 | csv.from('/tmp/data.csv').on('data', console.log); | |
| 29 | csv.from.path('/tmp/data.csv').on('data', console.log); | |
| 30 | */ | |
| 31 | ||
| 32 | ||
| 33 | 1 | module.exports = function(csv) { |
| 34 | /* | |
| 35 | ||
| 36 | `from(mixed)` | |
| 37 | ------------- | |
| 38 | ||
| 39 | Read from any sort of source. It should be considered as a convenient function which | |
| 40 | will discover the nature of the data source to parse. | |
| 41 | ||
| 42 | If it is a string, then if check if it match an existing file path and read the file content, | |
| 43 | otherwise, it treat the string as csv data. If it is an instance of stream, it consider the | |
| 44 | object to be an input stream. If is an array, then for each line should correspond a record. | |
| 45 | ||
| 46 | Here's some examples on how to use this function: | |
| 47 | ||
| 48 | csv() | |
| 49 | .from('"1","2","3","4"\n"a","b","c","d"') | |
| 50 | .on('end', function(){ console.log('done') }) | |
| 51 | ||
| 52 | csv() | |
| 53 | .from('./path/to/file.csv') | |
| 54 | .on('end', function(){ console.log('done') }) | |
| 55 | ||
| 56 | csv() | |
| 57 | .from(fs.createReadStream('./path/to/file.csv')) | |
| 58 | .on('end', function(){ console.log('done') }) | |
| 59 | ||
| 60 | csv() | |
| 61 | .from(['"1","2","3","4","5"',['1','2','3','4','5']]) | |
| 62 | .on('end', function(){ console.log('done') }) | |
| 63 | */ | |
| 64 | ||
| 65 | 78 | var from; |
| 66 | 78 | from = function(mixed, options) { |
| 67 | 13 | var error; |
| 68 | 13 | error = false; |
| 69 | 13 | switch (typeof mixed) { |
| 70 | case 'string': | |
| 71 | 9 | fs.exists(mixed, function(exists) { |
| 72 | 9 | if (exists) { |
| 73 | 1 | return from.path(mixed, options); |
| 74 | } else { | |
| 75 | 8 | return from.string(mixed, options); |
| 76 | } | |
| 77 | }); | |
| 78 | 9 | break; |
| 79 | case 'object': | |
| 80 | 4 | if (Array.isArray(mixed)) { |
| 81 | 3 | from.array(mixed, options); |
| 82 | } else { | |
| 83 | 1 | if (mixed instanceof Stream) { |
| 84 | 1 | from.stream(mixed, options); |
| 85 | } else { | |
| 86 | 0 | error = true; |
| 87 | } | |
| 88 | } | |
| 89 | 4 | break; |
| 90 | default: | |
| 91 | 0 | error = true; |
| 92 | } | |
| 93 | 13 | if (error) { |
| 94 | 0 | csv.error(new Error("Invalid mixed argument in from")); |
| 95 | } | |
| 96 | 13 | return csv; |
| 97 | }; | |
| 98 | /* | |
| 99 | ||
| 100 | `from.options([options])` | |
| 101 | ------------------------- | |
| 102 | ||
| 103 | Update and retrieve options relative to the input source. Return | |
| 104 | the options as an object if no argument is provided. | |
| 105 | ||
| 106 | * `delimiter` Set the field delimiter, one character only, defaults to comma. | |
| 107 | * `quote` Set the field delimiter, one character only, defaults to double quotes. | |
| 108 | * `escape` Set the field delimiter, one character only, defaults to double quotes. | |
| 109 | * `columns` List of fields or true if autodiscovered in the first CSV line, default to null. Impact the `transform` argument and the `data` event by providing an object instead of an array, order matters, see the transform and the columns sections for more details. | |
| 110 | * `flags` Used to read a file stream, default to the r charactere. | |
| 111 | * `encoding` Encoding of the read stream, defaults to 'utf8', applied when a readable stream is created. | |
| 112 | * `trim` If true, ignore whitespace immediately around the delimiter, defaults to false. | |
| 113 | * `ltrim` If true, ignore whitespace immediately following the delimiter (i.e. left-trim all fields), defaults to false. | |
| 114 | * `rtrim` If true, ignore whitespace immediately preceding the delimiter (i.e. right-trim all fields), defaults to false. | |
| 115 | ||
| 116 | Additionnaly, in case you are working with stream, you can pass all | |
| 117 | the options accepted by the `stream.pipe` function. | |
| 118 | */ | |
| 119 | ||
| 120 | 78 | from.options = function(options) { |
| 121 | 191 | if (options != null) { |
| 122 | 24 | utils.merge(csv.options.from, options); |
| 123 | 24 | return csv; |
| 124 | } else { | |
| 125 | 167 | return csv.options.from; |
| 126 | } | |
| 127 | }; | |
| 128 | /* | |
| 129 | ||
| 130 | `from.array(data, [options])` | |
| 131 | ------------------------------ | |
| 132 | ||
| 133 | Read from an array. Take an array as first argument and optionally | |
| 134 | some options as a second argument. Each element of the array | |
| 135 | represents a csv record. Those elements may be a string, a buffer, an | |
| 136 | array or an object. | |
| 137 | */ | |
| 138 | ||
| 139 | 78 | from.array = function(data, options) { |
| 140 | 8 | this.options(options); |
| 141 | 8 | process.nextTick(function() { |
| 142 | 8 | var record, _i, _len; |
| 143 | 8 | for (_i = 0, _len = data.length; _i < _len; _i++) { |
| 144 | 17 | record = data[_i]; |
| 145 | 17 | csv.write(record); |
| 146 | } | |
| 147 | 8 | return csv.end(); |
| 148 | }); | |
| 149 | 8 | return csv; |
| 150 | }; | |
| 151 | /* | |
| 152 | ||
| 153 | `from.string(data, [options])` | |
| 154 | ------------------------------- | |
| 155 | ||
| 156 | Read from a string or a buffer. Take a string as first argument and | |
| 157 | optionally an object of options as a second argument. The string | |
| 158 | must be the complete csv data, look at the streaming alternative if your | |
| 159 | CSV is large. | |
| 160 | ||
| 161 | csv() | |
| 162 | .from( '"1","2","3","4"\n"a","b","c","d"' ) | |
| 163 | .to( function(data){} ) | |
| 164 | */ | |
| 165 | ||
| 166 | 78 | from.string = function(data, options) { |
| 167 | 17 | this.options(options); |
| 168 | 17 | process.nextTick(function() { |
| 169 | 17 | csv.write(data); |
| 170 | 17 | return csv.end(); |
| 171 | }); | |
| 172 | 17 | return csv; |
| 173 | }; | |
| 174 | /* | |
| 175 | ||
| 176 | `from.path(path, [options])` | |
| 177 | ---------------------------- | |
| 178 | ||
| 179 | Read from a file path. Take a file path as first argument and optionally an object | |
| 180 | of options as a second argument. | |
| 181 | */ | |
| 182 | ||
| 183 | 78 | from.path = function(path, options) { |
| 184 | 40 | var stream; |
| 185 | 40 | this.options(options); |
| 186 | 40 | stream = fs.createReadStream(path, csv.from.options()); |
| 187 | 40 | return csv.from.stream(stream); |
| 188 | }; | |
| 189 | /* | |
| 190 | ||
| 191 | `from.stream(stream, [options])` | |
| 192 | -------------------------------- | |
| 193 | ||
| 194 | Read from a stream. Take a readable stream as first argument and optionally | |
| 195 | an object of options as a second argument. | |
| 196 | */ | |
| 197 | ||
| 198 | 78 | from.stream = function(stream, options) { |
| 199 | 43 | if (options) { |
| 200 | 0 | this.options(options); |
| 201 | } | |
| 202 | 43 | stream.setEncoding(csv.from.options().encoding); |
| 203 | 43 | stream.pipe(csv, csv.from.options()); |
| 204 | 43 | return csv; |
| 205 | }; | |
| 206 | 78 | return from; |
| 207 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | ||
| 3 | 1 | module.exports = { |
| 4 | merge: function(obj1, obj2) { | |
| 5 | 94 | var key, r; |
| 6 | 94 | r = obj1 || {}; |
| 7 | 94 | for (key in obj2) { |
| 8 | 618 | r[key] = obj2[key]; |
| 9 | } | |
| 10 | 94 | return r; |
| 11 | } | |
| 12 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | 1 | var Stream, fs, utils; |
| 3 | ||
| 4 | 1 | fs = require('fs'); |
| 5 | ||
| 6 | 1 | Stream = require('stream'); |
| 7 | ||
| 8 | 1 | utils = require('./utils'); |
| 9 | ||
| 10 | /* | |
| 11 | ||
| 12 | Writing data to a destination | |
| 13 | ============================= | |
| 14 | ||
| 15 | The `csv().to` property provides functions to read from a CSV instance and | |
| 16 | to write to an external destination. The destination may be a stream, a file | |
| 17 | or a callback. | |
| 18 | ||
| 19 | You may call the `to` function or one of its sub function. For example, | |
| 20 | here are two identical ways to write to a file: | |
| 21 | ||
| 22 | csv.from(data).to('/tmp/data.csv'); | |
| 23 | csv.from(data).to.path('/tmp/data.csv'); | |
| 24 | */ | |
| 25 | ||
| 26 | ||
| 27 | 1 | module.exports = function(csv) { |
| 28 | /* | |
| 29 | ||
| 30 | `to(mixed)` | |
| 31 | ----------- | |
| 32 | ||
| 33 | Write from any sort of destination. It should be considered as a convenient function | |
| 34 | which will discover the nature of the destination where to write the CSV data. | |
| 35 | ||
| 36 | If is an function, then the csv will be provided as the first argument | |
| 37 | of the callback. If it is a string, then it is expected to be a | |
| 38 | file path. If it is an instance of stream, it consider the object to be an | |
| 39 | output stream. | |
| 40 | ||
| 41 | Here's some examples on how to use this function: | |
| 42 | ||
| 43 | csv() | |
| 44 | .from('"1","2","3","4","5"') | |
| 45 | .to(function(data){ console.log(data) }) | |
| 46 | ||
| 47 | csv() | |
| 48 | .from('"1","2","3","4","5"') | |
| 49 | .to('./path/to/file.csv') | |
| 50 | ||
| 51 | csv() | |
| 52 | .from('"1","2","3","4","5"') | |
| 53 | .to(fs.createWriteStream('./path/to/file.csv')) | |
| 54 | */ | |
| 55 | ||
| 56 | 78 | var to; |
| 57 | 78 | to = function(mixed, options) { |
| 58 | 13 | var error; |
| 59 | 13 | error = false; |
| 60 | 13 | switch (typeof mixed) { |
| 61 | case 'string': | |
| 62 | 1 | to.path(mixed, options); |
| 63 | 1 | break; |
| 64 | case 'object': | |
| 65 | 0 | if (mixed instanceof Stream) { |
| 66 | 0 | to.stream(mixed, options); |
| 67 | } else { | |
| 68 | 0 | error = true; |
| 69 | } | |
| 70 | 0 | break; |
| 71 | case 'function': | |
| 72 | 12 | to.string(mixed, options); |
| 73 | 12 | break; |
| 74 | default: | |
| 75 | 0 | error = true; |
| 76 | } | |
| 77 | 13 | if (error) { |
| 78 | 0 | csv.error(new Error("Invalid mixed argument in from")); |
| 79 | } | |
| 80 | 13 | return csv; |
| 81 | }; | |
| 82 | /* | |
| 83 | ||
| 84 | `to.options([options])` | |
| 85 | ----------------------- | |
| 86 | ||
| 87 | Update and retrieve options relative to the output. Return the options | |
| 88 | as an object if no argument is provided. | |
| 89 | ||
| 90 | * `delimiter` Set the field delimiter, one character only, defaults to `options.from.delimiter` which is a comma. | |
| 91 | * `quote` Defaults to the quote read option. | |
| 92 | * `quoted` Boolean, default to false, quote all the fields even if not required. | |
| 93 | * `escape` Defaults to the escape read option. | |
| 94 | * `columns` List of fields, applied when `transform` returns an object, order matters, see the transform and the columns sections below. | |
| 95 | * `header` Display the column names on the first line if the columns option is provided. | |
| 96 | * `lineBreaks` String used to delimit record rows or a special value; special values are 'auto', 'unix', 'mac', 'windows', 'unicode'; defaults to 'auto' (discovered in source or 'unix' if no source is specified). | |
| 97 | * `flags` Defaults to 'w', 'w' to create or overwrite an file, 'a' to append to a file. Applied when using the `toPath` method. | |
| 98 | * `newColumns` If the `columns` option is not specified (which means columns will be taken from the reader options, will automatically append new columns if they are added during `transform()`. | |
| 99 | * `end` Prevent calling `end` on the destination, so that destination is no longer writable, similar to passing `{end: false}` option in `stream.pipe()`. | |
| 100 | */ | |
| 101 | ||
| 102 | 78 | to.options = function(options) { |
| 103 | 116 | if (options != null) { |
| 104 | 19 | utils.merge(csv.options.to, options); |
| 105 | 19 | return csv; |
| 106 | } else { | |
| 107 | 97 | return csv.options.to; |
| 108 | } | |
| 109 | }; | |
| 110 | /* | |
| 111 | ||
| 112 | `to.string(callback, [options])` | |
| 113 | ------------------------------ | |
| 114 | ||
| 115 | Provide the output string to a callback. | |
| 116 | ||
| 117 | csv() | |
| 118 | .from( '"1","2","3","4"\n"a","b","c","d"' ) | |
| 119 | .to( function(data, count){} ) | |
| 120 | ||
| 121 | Callback is called with 2 arguments: | |
| 122 | * data Stringify CSV string | |
| 123 | * count Number of stringified records | |
| 124 | */ | |
| 125 | ||
| 126 | 78 | to.string = function(callback, options) { |
| 127 | 13 | var data, stream; |
| 128 | 13 | this.options(options); |
| 129 | 13 | data = ''; |
| 130 | 13 | stream = new Stream; |
| 131 | 13 | stream.writable = true; |
| 132 | 13 | stream.write = function(d) { |
| 133 | 26 | data += d; |
| 134 | 26 | return true; |
| 135 | }; | |
| 136 | 13 | stream.end = function() { |
| 137 | 13 | return callback(data, csv.state.countWriten); |
| 138 | }; | |
| 139 | 13 | csv.pipe(stream); |
| 140 | 13 | return csv; |
| 141 | }; | |
| 142 | /* | |
| 143 | ||
| 144 | `to.stream(stream, [options])` | |
| 145 | ------------------------------ | |
| 146 | ||
| 147 | Write to a stream. Take a writable stream as first argument and | |
| 148 | optionally an object of options as a second argument. | |
| 149 | */ | |
| 150 | ||
| 151 | 78 | to.stream = function(stream, options) { |
| 152 | 52 | this.options(options); |
| 153 | 52 | switch (csv.options.to.lineBreaks) { |
| 154 | case 'auto': | |
| 155 | 0 | csv.options.to.lineBreaks = null; |
| 156 | 0 | break; |
| 157 | case 'unix': | |
| 158 | 2 | csv.options.to.lineBreaks = "\n"; |
| 159 | 2 | break; |
| 160 | case 'mac': | |
| 161 | 1 | csv.options.to.lineBreaks = "\r"; |
| 162 | 1 | break; |
| 163 | case 'windows': | |
| 164 | 1 | csv.options.to.lineBreaks = "\r\n"; |
| 165 | 1 | break; |
| 166 | case 'unicode': | |
| 167 | 1 | csv.options.to.lineBreaks = "\u2028"; |
| 168 | } | |
| 169 | 52 | csv.pipe(stream); |
| 170 | 52 | stream.on('error', function(e) { |
| 171 | 0 | return csv.error(e); |
| 172 | }); | |
| 173 | 52 | stream.on('close', function() { |
| 174 | 47 | return csv.emit('close', csv.state.count); |
| 175 | }); | |
| 176 | 52 | return csv; |
| 177 | }; | |
| 178 | /* | |
| 179 | ||
| 180 | `to.path(path, [options])` | |
| 181 | -------------------------- | |
| 182 | ||
| 183 | Write to a path. Take a file path as first argument and optionally an object of | |
| 184 | options as a second argument. The `close` event is sent after the file is written. | |
| 185 | Relying on the `end` event is incorrect because it is sent when parsing is done | |
| 186 | but before the file is written. | |
| 187 | */ | |
| 188 | ||
| 189 | 78 | to.path = function(path, options) { |
| 190 | 51 | var stream; |
| 191 | 51 | this.options(options); |
| 192 | 51 | options = utils.merge({}, csv.options.to); |
| 193 | 51 | delete options.end; |
| 194 | 51 | stream = fs.createWriteStream(path, options); |
| 195 | 51 | csv.to.stream(stream, null); |
| 196 | 51 | return csv; |
| 197 | }; | |
| 198 | 78 | return to; |
| 199 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | /* | |
| 3 | ||
| 4 | Stringifier | |
| 5 | =========== | |
| 6 | ||
| 7 | Convert an array or an object into a CSV line. | |
| 8 | */ | |
| 9 | ||
| 10 | 1 | var Stringifier; |
| 11 | ||
| 12 | 1 | Stringifier = function(csv) { |
| 13 | 78 | this.csv = csv; |
| 14 | 78 | return this; |
| 15 | }; | |
| 16 | ||
| 17 | /* | |
| 18 | Write a line to the written stream. Line may be an object, an array or a string | |
| 19 | The `preserve` argument is for line which are not considered as CSV data. | |
| 20 | */ | |
| 21 | ||
| 22 | ||
| 23 | 1 | Stringifier.prototype.write = function(line, preserve) { |
| 24 | 30491 | if (typeof line === 'undefined' || line === null) { |
| 25 | 9 | return; |
| 26 | } | |
| 27 | 30482 | if (!preserve) { |
| 28 | 30475 | try { |
| 29 | 30475 | this.csv.emit('record', line, this.csv.state.count - 1); |
| 30 | } catch (e) { | |
| 31 | 1 | return this.csv.error(e); |
| 32 | } | |
| 33 | 30474 | line = this.csv.stringifier.stringify(line); |
| 34 | } | |
| 35 | 30481 | this.csv.emit('data', line); |
| 36 | 30481 | if (!preserve) { |
| 37 | 30474 | this.csv.state.countWriten++; |
| 38 | } | |
| 39 | 30481 | return true; |
| 40 | }; | |
| 41 | ||
| 42 | 1 | Stringifier.prototype.stringify = function(line) { |
| 43 | 30474 | var column, columns, containsLinebreak, containsQuote, containsdelimiter, delimiter, escape, field, i, newLine, quote, regexp, _i, _j, _line, _ref, _ref1; |
| 44 | 30474 | columns = this.csv.options.to.columns || this.csv.options.from.columns; |
| 45 | 30474 | if (typeof columns === 'object' && columns !== null && !Array.isArray(columns)) { |
| 46 | 8 | columns = Object.keys(columns); |
| 47 | } | |
| 48 | 30474 | delimiter = this.csv.options.to.delimiter || this.csv.options.from.delimiter; |
| 49 | 30474 | quote = this.csv.options.to.quote || this.csv.options.from.quote; |
| 50 | 30474 | escape = this.csv.options.to.escape || this.csv.options.from.escape; |
| 51 | 30474 | if (typeof line === 'object') { |
| 52 | 29469 | if (!Array.isArray(line)) { |
| 53 | 1029 | _line = []; |
| 54 | 1029 | if (columns) { |
| 55 | 1023 | for (i = _i = 0, _ref = columns.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { |
| 56 | 3073 | column = columns[i]; |
| 57 | 3073 | _line[i] = typeof line[column] === 'undefined' || line[column] === null ? '' : line[column]; |
| 58 | } | |
| 59 | } else { | |
| 60 | 6 | for (column in line) { |
| 61 | 14 | _line.push(line[column]); |
| 62 | } | |
| 63 | } | |
| 64 | 1029 | line = _line; |
| 65 | 1029 | _line = null; |
| 66 | 28440 | } else if (columns) { |
| 67 | 11 | line.splice(columns.length); |
| 68 | } | |
| 69 | 29469 | if (Array.isArray(line)) { |
| 70 | 29469 | newLine = this.csv.state.countWriten ? this.csv.options.to.lineBreaks || "\n" : ''; |
| 71 | 29469 | for (i = _j = 0, _ref1 = line.length; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { |
| 72 | 220181 | field = line[i]; |
| 73 | 220181 | if (typeof field === 'string') { |
| 74 | ||
| 75 | 2027 | } else if (typeof field === 'number') { |
| 76 | 2017 | field = '' + field; |
| 77 | 10 | } else if (typeof field === 'boolean') { |
| 78 | 4 | field = field ? '1' : ''; |
| 79 | 6 | } else if (field instanceof Date) { |
| 80 | 0 | field = '' + field.getTime(); |
| 81 | } | |
| 82 | 220181 | if (field) { |
| 83 | 220151 | containsdelimiter = field.indexOf(delimiter) >= 0; |
| 84 | 220151 | containsQuote = field.indexOf(quote) >= 0; |
| 85 | 220151 | containsLinebreak = field.indexOf("\r") >= 0 || field.indexOf("\n") >= 0; |
| 86 | 220151 | if (containsQuote) { |
| 87 | 3021 | regexp = new RegExp(quote, 'g'); |
| 88 | 3021 | field = field.replace(regexp, escape + quote); |
| 89 | } | |
| 90 | 220151 | if (containsQuote || containsdelimiter || containsLinebreak || this.csv.options.to.quoted) { |
| 91 | 3035 | field = quote + field + quote; |
| 92 | } | |
| 93 | 220151 | newLine += field; |
| 94 | } | |
| 95 | 220181 | if (i !== line.length - 1) { |
| 96 | 190712 | newLine += delimiter; |
| 97 | } | |
| 98 | } | |
| 99 | 29469 | line = newLine; |
| 100 | } | |
| 101 | 1005 | } else if (typeof line === 'number') { |
| 102 | 1003 | line = '' + line; |
| 103 | } | |
| 104 | 30474 | return line; |
| 105 | }; | |
| 106 | ||
| 107 | 1 | module.exports = function(csv) { |
| 108 | 78 | return new Stringifier(csv); |
| 109 | }; | |
| 110 | ||
| 111 | 1 | module.exports.Stringifier = Stringifier; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | 1 | var EventEmitter, Parser; |
| 3 | ||
| 4 | 1 | EventEmitter = require('events').EventEmitter; |
| 5 | ||
| 6 | /* | |
| 7 | ||
| 8 | Parsing | |
| 9 | ======= | |
| 10 | ||
| 11 | The library extend the [EventEmitter][event] and emit the following events: | |
| 12 | ||
| 13 | * *row* | |
| 14 | Emitted by the parser on each line with the line content as an array of fields. | |
| 15 | * *end* | |
| 16 | Emitted when no more data will be parsed. | |
| 17 | * *error* | |
| 18 | Emitted when an error occured. | |
| 19 | */ | |
| 20 | ||
| 21 | ||
| 22 | 1 | Parser = function(csv) { |
| 23 | 78 | this.csv = csv; |
| 24 | 78 | this.options = csv.options.from; |
| 25 | 78 | this.state = csv.state; |
| 26 | 78 | this.quoted = false; |
| 27 | 78 | this.commented = false; |
| 28 | 78 | this.lines = 0; |
| 29 | 78 | return this; |
| 30 | }; | |
| 31 | ||
| 32 | 1 | Parser.prototype.__proto__ = EventEmitter.prototype; |
| 33 | ||
| 34 | /* | |
| 35 | ||
| 36 | `parse(chars)` | |
| 37 | -------------- | |
| 38 | ||
| 39 | Parse a string which may hold multiple lines. | |
| 40 | Private state object is enriched on each character until | |
| 41 | transform is called on a new line. | |
| 42 | */ | |
| 43 | ||
| 44 | ||
| 45 | 1 | Parser.prototype.parse = function(chars) { |
| 46 | 26420 | var c, csv, escapeIsQuote, i, isEscape, isQuote, isReallyEscaped, l, nextChar; |
| 47 | 26420 | csv = this.csv; |
| 48 | 26420 | chars = '' + chars; |
| 49 | 26420 | l = chars.length; |
| 50 | 26420 | i = 0; |
| 51 | 26420 | if (this.lines === 0 && csv.options.from.encoding === 'utf8' && 0xFEFF === chars.charCodeAt(0)) { |
| 52 | 1 | i++; |
| 53 | } | |
| 54 | 26420 | while (i < l) { |
| 55 | 2020024 | c = chars.charAt(i); |
| 56 | 2020024 | switch (c) { |
| 57 | case this.options.escape: | |
| 58 | case this.options.quote: | |
| 59 | 3198 | if (this.commented) { |
| 60 | 0 | break; |
| 61 | } | |
| 62 | 3198 | isReallyEscaped = false; |
| 63 | 3198 | if (c === this.options.escape) { |
| 64 | 3194 | nextChar = chars.charAt(i + 1); |
| 65 | 3194 | escapeIsQuote = this.options.escape === this.options.quote; |
| 66 | 3194 | isEscape = nextChar === this.options.escape; |
| 67 | 3194 | isQuote = nextChar === this.options.quote; |
| 68 | 3194 | if (!(escapeIsQuote && !this.state.field && !this.quoted) && (isEscape || isQuote)) { |
| 69 | 1012 | i++; |
| 70 | 1012 | isReallyEscaped = true; |
| 71 | 1012 | c = chars.charAt(i); |
| 72 | 1012 | this.state.field += c; |
| 73 | } | |
| 74 | } | |
| 75 | 3198 | if (!isReallyEscaped && c === this.options.quote) { |
| 76 | 2186 | if (this.state.field && !this.quoted) { |
| 77 | 3 | this.state.field += c; |
| 78 | 3 | break; |
| 79 | } | |
| 80 | 2183 | if (this.quoted) { |
| 81 | 1091 | nextChar = chars.charAt(i + 1); |
| 82 | 1091 | if (nextChar && nextChar !== '\r' && nextChar !== '\n' && nextChar !== this.options.delimiter) { |
| 83 | 2 | return this.error(new Error("Invalid closing quote at line " + (this.lines + 1) + "; found " + (JSON.stringify(nextChar)) + " instead of delimiter " + (JSON.stringify(this.options.delimiter)))); |
| 84 | } | |
| 85 | 1089 | this.quoted = false; |
| 86 | 1092 | } else if (this.state.field === '') { |
| 87 | 1092 | this.quoted = true; |
| 88 | } | |
| 89 | } | |
| 90 | 3193 | break; |
| 91 | case this.options.delimiter: | |
| 92 | 186756 | if (this.commented) { |
| 93 | 0 | break; |
| 94 | } | |
| 95 | 186756 | if (this.quoted) { |
| 96 | 9 | this.state.field += c; |
| 97 | } else { | |
| 98 | 186747 | if (this.options.trim || this.options.rtrim) { |
| 99 | 30 | this.state.field = this.state.field.trimRight(); |
| 100 | } | |
| 101 | 186747 | this.state.line.push(this.state.field); |
| 102 | 186747 | this.state.field = ''; |
| 103 | } | |
| 104 | 186756 | break; |
| 105 | case '\n': | |
| 106 | case '\r': | |
| 107 | 27445 | if (this.quoted) { |
| 108 | 5 | this.state.field += c; |
| 109 | 5 | break; |
| 110 | } | |
| 111 | 27440 | if (!this.options.quoted && this.state.lastC === '\r') { |
| 112 | 14 | break; |
| 113 | } | |
| 114 | 27426 | this.lines++; |
| 115 | 27426 | if (csv.options.to.lineBreaks === null) { |
| 116 | 46 | csv.options.to.lineBreaks = c + (c === '\r' && chars.charAt(i + 1) === '\n' ? '\n' : ''); |
| 117 | } | |
| 118 | 27426 | if (this.options.trim || this.options.rtrim) { |
| 119 | 5 | this.state.field = this.state.field.trimRight(); |
| 120 | } | |
| 121 | 27426 | this.state.line.push(this.state.field); |
| 122 | 27426 | this.state.field = ''; |
| 123 | 27426 | this.emit('row', this.state.line); |
| 124 | 27426 | this.state.line = []; |
| 125 | 27426 | break; |
| 126 | case ' ': | |
| 127 | case '\t': | |
| 128 | 1123 | if (this.quoted || (!this.options.trim && !this.options.ltrim) || this.state.field) { |
| 129 | 1082 | this.state.field += c; |
| 130 | 1082 | break; |
| 131 | } | |
| 132 | 41 | break; |
| 133 | default: | |
| 134 | 1801502 | if (this.commented) { |
| 135 | 0 | break; |
| 136 | } | |
| 137 | 1801502 | this.state.field += c; |
| 138 | } | |
| 139 | 2020022 | this.state.lastC = c; |
| 140 | 2020022 | i++; |
| 141 | } | |
| 142 | }; | |
| 143 | ||
| 144 | 1 | Parser.prototype.end = function() { |
| 145 | 74 | if (this.quoted) { |
| 146 | 1 | return this.error(new Error("Quoted field not terminated at line " + (this.lines + 1))); |
| 147 | } | |
| 148 | 73 | if (this.state.field || this.state.lastC === this.options.delimiter || this.state.lastC === this.options.quote) { |
| 149 | 24 | if (this.options.trim || this.options.rtrim) { |
| 150 | 1 | this.state.field = this.state.field.trimRight(); |
| 151 | } | |
| 152 | 24 | this.state.line.push(this.state.field); |
| 153 | 24 | this.state.field = ''; |
| 154 | } | |
| 155 | 73 | if (this.state.line.length > 0) { |
| 156 | 24 | this.emit('row', this.state.line); |
| 157 | } | |
| 158 | 73 | return this.emit('end', null); |
| 159 | }; | |
| 160 | ||
| 161 | 1 | Parser.prototype.error = function(e) { |
| 162 | 3 | return this.emit('error', e); |
| 163 | }; | |
| 164 | ||
| 165 | 1 | module.exports = function(csv) { |
| 166 | 78 | return new Parser(csv); |
| 167 | }; | |
| 168 | ||
| 169 | 1 | module.exports.Parser = Parser; |
| 170 | ||
| 171 | /* | |
| 172 | [event]: http://nodejs.org/api/events.html | |
| 173 | */ | |
| 174 |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | 1 | var Transformer, stream; |
| 3 | ||
| 4 | 1 | stream = require('stream'); |
| 5 | ||
| 6 | /* | |
| 7 | Transforming data | |
| 8 | ================= | |
| 9 | ||
| 10 | Transformation may occur synchronously or asynchronously dependending | |
| 11 | on the provided transform callback and its declared arguments length. | |
| 12 | ||
| 13 | Callback are called for each line and its arguments are : | |
| 14 | ||
| 15 | * *data* | |
| 16 | CSV record | |
| 17 | * *index* | |
| 18 | Incremented counter | |
| 19 | * *callback* | |
| 20 | Callback function to be called in asynchronous mode | |
| 21 | ||
| 22 | Unless you specify the `columns` read option, `data` are provided | |
| 23 | as arrays, otherwise they are objects with keys matching columns | |
| 24 | names. | |
| 25 | ||
| 26 | In synchronous mode, the contract is quite simple, you receive an array | |
| 27 | of fields for each record and return the transformed record. | |
| 28 | ||
| 29 | In asynchronous mode, it is your responsibility to call the callback | |
| 30 | provided as the third argument. It must be called with two arguments, | |
| 31 | the first one is an error if any, the second is the transformed record. | |
| 32 | ||
| 33 | Transformed records may be an array, an associative array, a | |
| 34 | string or null. If null, the record will simply be skipped. When the | |
| 35 | returned value is an array, the fields are merged in order. | |
| 36 | When the returned value is an object, it will search for | |
| 37 | the `columns` property in the write or in the read options and | |
| 38 | smartly order the values. If no `columns` options are found, | |
| 39 | it will merge the values in their order of appearance. When the | |
| 40 | returned value is a string, it is directly sent to the destination | |
| 41 | source and it is your responsibility to delimit, quote, escape | |
| 42 | or define line breaks. | |
| 43 | ||
| 44 | Transform callback run synchronously: | |
| 45 | ||
| 46 | csv() | |
| 47 | .from('82,Preisner,Zbigniew\n94,Gainsbourg,Serge') | |
| 48 | .to(console.log) | |
| 49 | .transform(function(data, index){ | |
| 50 | return data.reverse() | |
| 51 | }); | |
| 52 | // Executing `node samples/transform.js`, print: | |
| 53 | // 94,Gainsbourg,Serge\n82,Preisner,Zbigniew | |
| 54 | ||
| 55 | Transform callback run asynchronously: | |
| 56 | ||
| 57 | csv() | |
| 58 | .from('82,Preisner,Zbigniew\n94,Gainsbourg,Serge') | |
| 59 | .to(console.log) | |
| 60 | .transform(function(data, index, callback){ | |
| 61 | process.nextTick(function(){ | |
| 62 | callback(null, data.reverse()); | |
| 63 | }); | |
| 64 | }); | |
| 65 | // Executing `node samples/transform.js`, print: | |
| 66 | // 94,Gainsbourg,Serge\n82,Preisner,Zbigniew | |
| 67 | ||
| 68 | Transform callback returning a string: | |
| 69 | ||
| 70 | csv() | |
| 71 | .from('82,Preisner,Zbigniew\n94,Gainsbourg,Serge') | |
| 72 | .to(console.log) | |
| 73 | .transform(function(data, index){ | |
| 74 | return (index>0 ? ',' : '') + data[0] + ":" + data[2] + ' ' + data[1]; | |
| 75 | }); | |
| 76 | // Executing `node samples/transform.js`, print: | |
| 77 | // 82:Zbigniew Preisner,94:Serge Gainsbourg | |
| 78 | */ | |
| 79 | ||
| 80 | ||
| 81 | 1 | Transformer = function(csv) { |
| 82 | 78 | this.csv = csv; |
| 83 | 78 | return this; |
| 84 | }; | |
| 85 | ||
| 86 | 1 | Transformer.prototype.__proto__ = stream.prototype; |
| 87 | ||
| 88 | /* no doc | |
| 89 | ||
| 90 | `transformer(csv).transform(line)` | |
| 91 | ---------------------------------- | |
| 92 | ||
| 93 | Call a callback to transform a line. Called from the `parse` function on each | |
| 94 | line. It is responsible for transforming the data and finally calling `write`. | |
| 95 | */ | |
| 96 | ||
| 97 | ||
| 98 | 1 | Transformer.prototype.transform = function(line) { |
| 99 | 30482 | var column, columns, csv, done, finish, i, lineAsObject, sync, _i, _j, _len, _len1; |
| 100 | 30482 | csv = this.csv; |
| 101 | 30482 | columns = csv.options.from.columns; |
| 102 | 30482 | if (columns) { |
| 103 | 23 | if (typeof columns === 'object' && columns !== null && !Array.isArray(columns)) { |
| 104 | 2 | columns = Object.keys(columns); |
| 105 | } | |
| 106 | 23 | if (csv.state.count === 0 && columns === true) { |
| 107 | 6 | csv.options.from.columns = line; |
| 108 | 6 | return; |
| 109 | } | |
| 110 | 17 | if (Array.isArray(line)) { |
| 111 | 11 | lineAsObject = {}; |
| 112 | 11 | for (i = _i = 0, _len = columns.length; _i < _len; i = ++_i) { |
| 113 | 58 | column = columns[i]; |
| 114 | 58 | lineAsObject[column] = line[i] || null; |
| 115 | } | |
| 116 | 11 | line = lineAsObject; |
| 117 | } else { | |
| 118 | 6 | lineAsObject = {}; |
| 119 | 6 | for (i = _j = 0, _len1 = columns.length; _j < _len1; i = ++_j) { |
| 120 | 12 | column = columns[i]; |
| 121 | 12 | lineAsObject[column] = line[column] || null; |
| 122 | } | |
| 123 | 6 | line = lineAsObject; |
| 124 | } | |
| 125 | } | |
| 126 | 30476 | finish = (function(line) { |
| 127 | 30475 | var k, v; |
| 128 | 30475 | if (csv.state.count === 1 && csv.options.to.header === true) { |
| 129 | 7 | columns = csv.options.to.columns || csv.options.from.columns; |
| 130 | 7 | if (typeof columns === 'object') { |
| 131 | 7 | columns = (function() { |
| 132 | 7 | var _results; |
| 133 | 7 | _results = []; |
| 134 | 7 | for (k in columns) { |
| 135 | 19 | v = columns[k]; |
| 136 | 19 | _results.push(v); |
| 137 | } | |
| 138 | 7 | return _results; |
| 139 | })(); | |
| 140 | } | |
| 141 | 7 | csv.stringifier.write(columns); |
| 142 | } | |
| 143 | 30475 | csv.stringifier.write(line); |
| 144 | 30475 | if (csv.state.transforming === 0 && this.closed === true) { |
| 145 | 3 | return this.emit('end', csv.state.count); |
| 146 | } | |
| 147 | }).bind(this); | |
| 148 | 30476 | csv.state.count++; |
| 149 | 30476 | if (this.callback) { |
| 150 | 1124 | sync = this.callback.length !== 3; |
| 151 | 1124 | csv.state.transforming++; |
| 152 | 1124 | done = function(err, line) { |
| 153 | 1124 | var isObject; |
| 154 | 1124 | if (err) { |
| 155 | 1 | return csv.error(err); |
| 156 | } | |
| 157 | 1123 | isObject = typeof line === 'object' && !Array.isArray(line); |
| 158 | 1123 | if (csv.options.to.newColumns && !csv.options.to.columns && isObject) { |
| 159 | 2 | Object.keys(line).filter(function(column) { |
| 160 | 14 | return columns.indexOf(column) === -1; |
| 161 | }).forEach(function(column) { | |
| 162 | 1 | return columns.push(column); |
| 163 | }); | |
| 164 | } | |
| 165 | 1123 | csv.state.transforming--; |
| 166 | 1123 | return finish(line); |
| 167 | }; | |
| 168 | 1124 | if (sync) { |
| 169 | 1116 | try { |
| 170 | 1116 | return done(null, this.callback(line, csv.state.count - 1)); |
| 171 | } catch (err) { | |
| 172 | 1 | return done(err); |
| 173 | } | |
| 174 | } else { | |
| 175 | 8 | try { |
| 176 | 8 | return this.callback(line, csv.state.count - 1, function(err, line) { |
| 177 | 8 | return done(err, line); |
| 178 | }); | |
| 179 | } catch (_error) {} | |
| 180 | } | |
| 181 | } else { | |
| 182 | 29352 | return finish(line); |
| 183 | } | |
| 184 | }; | |
| 185 | ||
| 186 | /* no doc | |
| 187 | `transformer(csv).end()` | |
| 188 | ------------------------ | |
| 189 | ||
| 190 | A transformer instance extends the EventEmitter and | |
| 191 | emit the 'end' event when the last callback is called. | |
| 192 | */ | |
| 193 | ||
| 194 | ||
| 195 | 1 | Transformer.prototype.end = function() { |
| 196 | 73 | if (this.closed) { |
| 197 | 0 | return this.csv.error(new Error('Transformer already closed')); |
| 198 | } | |
| 199 | 73 | this.closed = true; |
| 200 | 73 | if (this.csv.state.transforming === 0) { |
| 201 | 70 | return this.emit('end'); |
| 202 | } | |
| 203 | }; | |
| 204 | ||
| 205 | 1 | module.exports = function(csv) { |
| 206 | 78 | return new Transformer(csv); |
| 207 | }; | |
| 208 | ||
| 209 | 1 | module.exports.Transformer = Transformer; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Generated by CoffeeScript 1.3.3 | |
| 2 | 1 | var Generator, Stream, util; |
| 3 | ||
| 4 | 1 | Stream = require('stream'); |
| 5 | ||
| 6 | 1 | util = require('util'); |
| 7 | ||
| 8 | /* | |
| 9 | ||
| 10 | `generator([options])`: Generate random CSV data | |
| 11 | ================================================ | |
| 12 | ||
| 13 | This function is provided for conveniency in case you need to generate random CSV data. | |
| 14 | ||
| 15 | Note, it is quite simple at the moment, more functionnalities could come later. The code | |
| 16 | originates from "./samples/perf.coffee" and was later extracted in case other persons need | |
| 17 | its functionnalities. | |
| 18 | ||
| 19 | Options may include | |
| 20 | ||
| 21 | * duration Period to run in milliseconds, default to 4 minutes. | |
| 22 | * nb_columns Number of fields per record | |
| 23 | * max_word_length Maximum number of characters per word | |
| 24 | * start Start the generation on next tick, otherwise you must call resume | |
| 25 | ||
| 26 | Starting a generation | |
| 27 | ||
| 28 | csv = require 'csv' | |
| 29 | generator = csv.generator | |
| 30 | generator(start: true).pipe csv().to.path "#{__dirname}/perf.out" | |
| 31 | */ | |
| 32 | ||
| 33 | ||
| 34 | 1 | Generator = function(options) { |
| 35 | 2 | var _base, _base1, _ref, _ref1; |
| 36 | 2 | this.options = options != null ? options : {}; |
| 37 | 2 | if ((_ref = (_base = this.options).duration) == null) { |
| 38 | 0 | _base.duration = 4 * 60 * 1000; |
| 39 | } | |
| 40 | 2 | this.options.nb_columns = 8; |
| 41 | 2 | if ((_ref1 = (_base1 = this.options).max_word_length) == null) { |
| 42 | 2 | _base1.max_word_length = 16; |
| 43 | } | |
| 44 | 2 | this.start = Date.now(); |
| 45 | 2 | this.end = this.start + this.options.duration; |
| 46 | 2 | this.readable = true; |
| 47 | 2 | if (this.options.start) { |
| 48 | 2 | process.nextTick(this.resume.bind(this)); |
| 49 | } | |
| 50 | 2 | return this; |
| 51 | }; | |
| 52 | ||
| 53 | 1 | Generator.prototype.__proto__ = Stream.prototype; |
| 54 | ||
| 55 | 1 | Generator.prototype.resume = function() { |
| 56 | 15312 | var char, column, line, nb_chars, nb_words, _i, _j, _ref, _ref1; |
| 57 | 15312 | this.paused = false; |
| 58 | 15312 | while (!this.paused && this.readable) { |
| 59 | 26288 | if (Date.now() > this.end) { |
| 60 | 2 | return this.destroy(); |
| 61 | } | |
| 62 | 26286 | line = []; |
| 63 | 26286 | for (nb_words = _i = 0, _ref = this.options.nb_columns; 0 <= _ref ? _i < _ref : _i > _ref; nb_words = 0 <= _ref ? ++_i : --_i) { |
| 64 | 210288 | column = []; |
| 65 | 210288 | for (nb_chars = _j = 0, _ref1 = Math.ceil(Math.random() * this.options.max_word_length); 0 <= _ref1 ? _j < _ref1 : _j > _ref1; nb_chars = 0 <= _ref1 ? ++_j : --_j) { |
| 66 | 1785881 | char = Math.floor(Math.random() * 32); |
| 67 | 1785881 | column.push(String.fromCharCode(char + (char < 16 ? 65 : 97 - 16))); |
| 68 | } | |
| 69 | 210288 | line.push(column.join('')); |
| 70 | } | |
| 71 | 26286 | this.emit('data', new Buffer("" + (line.join(',')) + "\n", this.options.encoding)); |
| 72 | } | |
| 73 | }; | |
| 74 | ||
| 75 | 1 | Generator.prototype.pause = function() { |
| 76 | 15310 | return this.paused = true; |
| 77 | }; | |
| 78 | ||
| 79 | 1 | Generator.prototype.destroy = function() { |
| 80 | 2 | this.readable = false; |
| 81 | 2 | this.emit('end'); |
| 82 | 2 | return this.emit('close'); |
| 83 | }; | |
| 84 | ||
| 85 | /* | |
| 86 | `setEncoding([encoding])` | |
| 87 | ||
| 88 | Makes the 'data' event emit a string instead of a Buffer. | |
| 89 | encoding can be 'utf8', 'utf16le' ('ucs2'), 'ascii', or | |
| 90 | 'hex'. Defaults to 'utf8'. | |
| 91 | */ | |
| 92 | ||
| 93 | ||
| 94 | 1 | Generator.prototype.setEncoding = function(encoding) { |
| 95 | 1 | return this.options.encoding = encoding; |
| 96 | }; | |
| 97 | ||
| 98 | 1 | module.exports = function(options) { |
| 99 | 2 | return new Generator(options); |
| 100 | }; | |
| 101 | ||
| 102 | 1 | module.exports.Generator = Generator; |