| 1 | | // Generated by CoffeeScript 1.3.3 |
| 2 | | /* |
| 3 | | Module CSV - Copyright David Worms <open@adaltas.com> (BSD Licensed) |
| 4 | | |
| 5 | | |---------------| |---------------|---------------| |---------------| |
| 6 | | | | | | | | | |
| 7 | | | | | CSV | | | |
| 8 | | | | | | | | | |
| 9 | | | Stream | | Writer | Reader | | Stream | |
| 10 | | | Reader | .pipe( | API | API | ).pipe( | Writer | ) |
| 11 | | | | | | | | | |
| 12 | | | | | | | | | |
| 13 | | |---------------| |---------------|---------------| |---------------| |
| 14 | | |
| 15 | | fs.createReadStream('in'.pipe( csv() ).pipe( fs.createWriteStream('out') ) |
| 16 | | */ |
| 17 | | |
| 18 | 1 | var from, options, state, stream, to; |
| 19 | | |
| 20 | 1 | stream = require('stream'); |
| 21 | | |
| 22 | 1 | state = require('./state'); |
| 23 | | |
| 24 | 1 | options = require('./options'); |
| 25 | | |
| 26 | 1 | from = require('./from'); |
| 27 | | |
| 28 | 1 | to = require('./to'); |
| 29 | | |
| 30 | 1 | module.exports = function() { |
| 31 | 11 | var CSV, csv, error, parse, transform, write; |
| 32 | 11 | CSV = function() { |
| 33 | 11 | this.readable = true; |
| 34 | 11 | this.writable = true; |
| 35 | 11 | this.state = state(); |
| 36 | 11 | this.options = options(); |
| 37 | 11 | this.from = from(this); |
| 38 | 11 | this.to = to(this); |
| 39 | 11 | return this; |
| 40 | | }; |
| 41 | 11 | CSV.prototype.__proto__ = stream.prototype; |
| 42 | | /* |
| 43 | | |
| 44 | | `write(data, [preserve])`: Write data |
| 45 | | ------------------------------------- |
| 46 | | |
| 47 | | Implementation of the StreamWriter API with a larger signature. Data |
| 48 | | may be a string, a buffer, an array or an object. |
| 49 | | |
| 50 | | If data is a string or a buffer, it could span multiple lines. If data |
| 51 | | is an object or an array, it must represent a single line. |
| 52 | | Preserve is for line which are not considered as CSV data. |
| 53 | | */ |
| 54 | | |
| 55 | 11 | CSV.prototype.write = function(data, preserve) { |
| 56 | 12 | if (!this.writable) { |
| 57 | 0 | return; |
| 58 | | } |
| 59 | 12 | if (typeof data === 'string' && !preserve) { |
| 60 | 12 | return parse(data); |
| 61 | 0 | } else if (Array.isArray(data) && !this.state.transforming) { |
| 62 | 0 | this.state.line = data; |
| 63 | 0 | return transform(); |
| 64 | | } |
| 65 | 0 | if (this.state.count === 0 && this.options.to.header === true) { |
| 66 | 0 | write(this.options.to.columns || this.options.from.columns); |
| 67 | | } |
| 68 | 0 | write(data, preserve); |
| 69 | 0 | if (!this.state.transforming && !preserve) { |
| 70 | 0 | return this.state.count++; |
| 71 | | } |
| 72 | | }; |
| 73 | | /* |
| 74 | | |
| 75 | | `end()`: Terminate the parsing |
| 76 | | ------------------------------- |
| 77 | | |
| 78 | | Call this method when no more csv data is to be parsed. It |
| 79 | | implement the StreamWriter API by setting the `writable` |
| 80 | | property to "false" and emitting the `end` event. |
| 81 | | */ |
| 82 | | |
| 83 | 11 | CSV.prototype.end = function() { |
| 84 | 11 | if (!this.writable) { |
| 85 | 0 | return; |
| 86 | | } |
| 87 | 11 | if (this.state.quoted) { |
| 88 | 0 | return error(new Error('Quoted field not terminated')); |
| 89 | | } |
| 90 | 11 | if (this.state.field || this.state.lastC === this.options.from.delimiter || this.state.lastC === this.options.from.quote) { |
| 91 | 1 | if (this.options.from.trim || this.options.from.rtrim) { |
| 92 | 0 | this.state.field = this.state.field.trimRight(); |
| 93 | | } |
| 94 | 1 | this.state.line.push(this.state.field); |
| 95 | 1 | this.state.field = ''; |
| 96 | | } |
| 97 | 11 | if (this.state.line.length > 0) { |
| 98 | 1 | transform(); |
| 99 | | } |
| 100 | 11 | if (this.writeStream) { |
| 101 | 11 | if (this.state.bufferPosition !== 0) { |
| 102 | 11 | this.writeStream.write(this.state.buffer.slice(0, this.state.bufferPosition)); |
| 103 | | } |
| 104 | 11 | if (this.options.to.end) { |
| 105 | 11 | return this.writeStream.end(); |
| 106 | | } else { |
| 107 | 0 | this.emit('end', this.state.count); |
| 108 | 0 | return this.readable = false; |
| 109 | | } |
| 110 | | } else { |
| 111 | 0 | this.emit('end', this.state.count); |
| 112 | 0 | return this.readable = false; |
| 113 | | } |
| 114 | | }; |
| 115 | | /* |
| 116 | | |
| 117 | | `transform(callback)`: Register the transformer callback |
| 118 | | -------------------------------------------------------- |
| 119 | | |
| 120 | | User provided function call on each line to filter, enrich or modify |
| 121 | | the dataset. The callback is called asynchronously. |
| 122 | | */ |
| 123 | | |
| 124 | 11 | CSV.prototype.transform = function(callback) { |
| 125 | 8 | this.transformer = callback; |
| 126 | 8 | return this; |
| 127 | | }; |
| 128 | 11 | csv = new CSV(); |
| 129 | | /* |
| 130 | | Parse a string which may hold multiple lines. |
| 131 | | Private state object is enriched on each character until |
| 132 | | transform is called on a new line |
| 133 | | */ |
| 134 | | |
| 135 | 11 | parse = function(chars) { |
| 136 | 12 | var c, escapeIsQuoted, i, isEscaped, isQuoted, isReallyEscaped, l, nextChar; |
| 137 | 12 | chars = '' + chars; |
| 138 | 12 | l = chars.length; |
| 139 | 12 | i = 0; |
| 140 | 12 | while (i < l) { |
| 141 | 3270 | c = chars.charAt(i); |
| 142 | 3270 | switch (c) { |
| 143 | | case csv.options.from.escape: |
| 144 | | case csv.options.from.quote: |
| 145 | 14 | if (csv.state.commented) { |
| 146 | 0 | break; |
| 147 | | } |
| 148 | 14 | isReallyEscaped = false; |
| 149 | 14 | if (c === csv.options.from.escape) { |
| 150 | 10 | nextChar = chars.charAt(i + 1); |
| 151 | 10 | escapeIsQuoted = csv.options.from.escape === csv.options.from.quote; |
| 152 | 10 | isEscaped = nextChar === csv.options.from.escape; |
| 153 | 10 | isQuoted = nextChar === csv.options.from.quote; |
| 154 | 10 | if (!(escapeIsQuoted && !csv.state.field && !csv.state.quoted) && (isEscaped || isQuoted)) { |
| 155 | 6 | i++; |
| 156 | 6 | isReallyEscaped = true; |
| 157 | 6 | c = chars.charAt(i); |
| 158 | 6 | csv.state.field += c; |
| 159 | | } |
| 160 | | } |
| 161 | 14 | if (!isReallyEscaped && c === csv.options.from.quote) { |
| 162 | 8 | if (csv.state.field && !csv.state.quoted) { |
| 163 | 0 | csv.state.field += c; |
| 164 | 0 | break; |
| 165 | | } |
| 166 | 8 | if (csv.state.quoted) { |
| 167 | 4 | nextChar = chars.charAt(i + 1); |
| 168 | 4 | if (nextChar && nextChar !== '\r' && nextChar !== '\n' && nextChar !== csv.options.from.delimiter) { |
| 169 | 0 | return error(new Error('Invalid closing quote; found "' + nextChar + '" instead of delimiter "' + csv.options.from.delimiter + '"')); |
| 170 | | } |
| 171 | 4 | csv.state.quoted = false; |
| 172 | 4 | } else if (csv.state.field === '') { |
| 173 | 4 | csv.state.quoted = true; |
| 174 | | } |
| 175 | | } |
| 176 | 14 | break; |
| 177 | | case csv.options.from.delimiter: |
| 178 | 341 | if (csv.state.commented) { |
| 179 | 0 | break; |
| 180 | | } |
| 181 | 341 | if (csv.state.quoted) { |
| 182 | 0 | csv.state.field += c; |
| 183 | | } else { |
| 184 | 341 | if (csv.options.from.trim || csv.options.from.rtrim) { |
| 185 | 0 | csv.state.field = csv.state.field.trimRight(); |
| 186 | | } |
| 187 | 341 | csv.state.line.push(csv.state.field); |
| 188 | 341 | csv.state.field = ''; |
| 189 | | } |
| 190 | 341 | break; |
| 191 | | case '\n': |
| 192 | | case '\r': |
| 193 | 68 | if (csv.state.quoted) { |
| 194 | 0 | csv.state.field += c; |
| 195 | 0 | break; |
| 196 | | } |
| 197 | 68 | if (!csv.options.from.quoted && csv.state.lastC === '\r') { |
| 198 | 0 | break; |
| 199 | | } |
| 200 | 68 | if (csv.options.to.lineBreaks === null) { |
| 201 | 11 | csv.options.to.lineBreaks = c + (c === '\r' && chars.charAt(i + 1) === '\n' ? '\n' : ''); |
| 202 | | } |
| 203 | 68 | if (csv.options.from.trim || csv.options.from.rtrim) { |
| 204 | 0 | csv.state.field = csv.state.field.trimRight(); |
| 205 | | } |
| 206 | 68 | csv.state.line.push(csv.state.field); |
| 207 | 68 | csv.state.field = ''; |
| 208 | 68 | transform(); |
| 209 | 68 | break; |
| 210 | | case ' ': |
| 211 | | case '\t': |
| 212 | 0 | if (csv.state.quoted || (!csv.options.from.trim && !csv.options.from.ltrim) || csv.state.field) { |
| 213 | 0 | csv.state.field += c; |
| 214 | 0 | break; |
| 215 | | } |
| 216 | 0 | break; |
| 217 | | default: |
| 218 | 2847 | if (csv.state.commented) { |
| 219 | 0 | break; |
| 220 | | } |
| 221 | 2847 | csv.state.field += c; |
| 222 | | } |
| 223 | 3270 | csv.state.lastC = c; |
| 224 | 3270 | i++; |
| 225 | | } |
| 226 | | }; |
| 227 | | /* |
| 228 | | Called by the `parse` function on each line. It is responsible for |
| 229 | | transforming the data and finally calling `write`. |
| 230 | | */ |
| 231 | | |
| 232 | 11 | transform = function() { |
| 233 | 69 | var column, columns, i, isObject, line, _i, _ref; |
| 234 | 69 | line = null; |
| 235 | 69 | columns = csv.options.from.columns; |
| 236 | 69 | if (columns) { |
| 237 | 8 | if (csv.state.count === 0 && columns === true) { |
| 238 | 2 | csv.options.from.columns = columns = csv.state.line; |
| 239 | 2 | csv.state.line = []; |
| 240 | 2 | csv.state.lastC = ''; |
| 241 | 2 | return; |
| 242 | | } |
| 243 | 6 | line = {}; |
| 244 | 6 | for (i = _i = 0, _ref = columns.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { |
| 245 | 37 | column = columns[i]; |
| 246 | 37 | line[column] = csv.state.line[i] || null; |
| 247 | | } |
| 248 | 6 | csv.state.line = line; |
| 249 | 6 | line = null; |
| 250 | | } |
| 251 | 67 | if (csv.transformer) { |
| 252 | 61 | csv.state.transforming = true; |
| 253 | 61 | try { |
| 254 | 61 | line = csv.transformer(csv.state.line, csv.state.count); |
| 255 | | } catch (e) { |
| 256 | 0 | return error(e); |
| 257 | | } |
| 258 | 61 | isObject = typeof line === 'object' && !Array.isArray(line); |
| 259 | 61 | if (csv.options.to.newColumns && !csv.options.to.columns && isObject) { |
| 260 | 2 | Object.keys(line).filter(function(column) { |
| 261 | 14 | return columns.indexOf(column) === -1; |
| 262 | | }).forEach(function(column) { |
| 263 | 1 | return columns.push(column); |
| 264 | | }); |
| 265 | | } |
| 266 | 61 | csv.state.transforming = false; |
| 267 | | } else { |
| 268 | 6 | line = csv.state.line; |
| 269 | | } |
| 270 | 67 | if (csv.state.count === 0 && csv.options.to.header === true) { |
| 271 | 1 | write(csv.options.to.columns || columns); |
| 272 | | } |
| 273 | 67 | write(line); |
| 274 | 67 | csv.state.count++; |
| 275 | 67 | csv.state.line = []; |
| 276 | 67 | return csv.state.lastC = ''; |
| 277 | | }; |
| 278 | | /* |
| 279 | | Write a line to the written stream. |
| 280 | | Line may be an object, an array or a string |
| 281 | | Preserve is for line which are not considered as CSV data |
| 282 | | */ |
| 283 | | |
| 284 | 11 | write = function(line, preserve) { |
| 285 | 68 | var column, columns, containsLinebreak, containsQuote, containsdelimiter, field, i, newLine, regexp, _i, _j, _line, _ref, _ref1; |
| 286 | 68 | if (typeof line === 'undefined' || line === null) { |
| 287 | 0 | return; |
| 288 | | } |
| 289 | 68 | if (!preserve) { |
| 290 | 68 | try { |
| 291 | 68 | csv.emit('data', line, csv.state.count); |
| 292 | | } catch (e) { |
| 293 | 0 | return error(e); |
| 294 | | } |
| 295 | | } |
| 296 | 68 | if (typeof line === 'object') { |
| 297 | 68 | if (!Array.isArray(line)) { |
| 298 | 8 | columns = csv.options.to.columns || csv.options.from.columns; |
| 299 | 8 | _line = []; |
| 300 | 8 | if (columns) { |
| 301 | 8 | for (i = _i = 0, _ref = columns.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { |
| 302 | 42 | column = columns[i]; |
| 303 | 42 | _line[i] = typeof line[column] === 'undefined' || line[column] === null ? '' : line[column]; |
| 304 | | } |
| 305 | | } else { |
| 306 | 0 | for (column in line) { |
| 307 | 0 | _line.push(line[column]); |
| 308 | | } |
| 309 | | } |
| 310 | 8 | line = _line; |
| 311 | 8 | _line = null; |
| 312 | 60 | } else if (csv.options.to.columns) { |
| 313 | 2 | line.splice(csv.options.to.columns.length); |
| 314 | | } |
| 315 | 68 | if (Array.isArray(line)) { |
| 316 | 68 | newLine = csv.state.countWriten ? csv.options.to.lineBreaks || "\n" : ''; |
| 317 | 68 | for (i = _j = 0, _ref1 = line.length; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { |
| 318 | 391 | field = line[i]; |
| 319 | 391 | if (typeof field === 'string') { |
| 320 | | |
| 321 | 0 | } else if (typeof field === 'number') { |
| 322 | 0 | field = '' + field; |
| 323 | 0 | } else if (typeof field === 'boolean') { |
| 324 | 0 | field = field ? '1' : ''; |
| 325 | 0 | } else if (field instanceof Date) { |
| 326 | 0 | field = '' + field.getTime(); |
| 327 | | } |
| 328 | 391 | if (field) { |
| 329 | 381 | containsdelimiter = field.indexOf(csv.options.to.delimiter || csv.options.from.delimiter) >= 0; |
| 330 | 381 | containsQuote = field.indexOf(csv.options.to.quote || csv.options.from.quote) >= 0; |
| 331 | 381 | containsLinebreak = field.indexOf("\r") >= 0 || field.indexOf("\n") >= 0; |
| 332 | 381 | if (containsQuote) { |
| 333 | 4 | regexp = new RegExp(csv.options.to.quote || csv.options.from.quote, 'g'); |
| 334 | 4 | field = field.replace(regexp, (csv.options.to.escape || csv.options.from.escape) + (csv.options.to.quote || csv.options.from.quote)); |
| 335 | | } |
| 336 | 381 | if (containsQuote || containsdelimiter || containsLinebreak || csv.options.to.quoted) { |
| 337 | 4 | field = (csv.options.to.quote || csv.options.from.quote) + field + (csv.options.to.quote || csv.options.from.quote); |
| 338 | | } |
| 339 | 381 | newLine += field; |
| 340 | | } |
| 341 | 391 | if (i !== line.length - 1) { |
| 342 | 323 | newLine += csv.options.to.delimiter || csv.options.from.delimiter; |
| 343 | | } |
| 344 | | } |
| 345 | 68 | line = newLine; |
| 346 | | } |
| 347 | 0 | } else if (typeof line === 'number') { |
| 348 | 0 | line = '' + line; |
| 349 | | } |
| 350 | 68 | if (csv.state.buffer) { |
| 351 | 68 | if (csv.state.bufferPosition + Buffer.byteLength(line, csv.options.to.encoding) > csv.options.from.bufferSize) { |
| 352 | 1 | csv.writeStream.write(csv.state.buffer.slice(0, csv.state.bufferPosition)); |
| 353 | 1 | csv.state.buffer = new Buffer(csv.options.from.bufferSize); |
| 354 | 1 | csv.state.bufferPosition = 0; |
| 355 | | } |
| 356 | 68 | csv.state.bufferPosition += csv.state.buffer.write(line, csv.state.bufferPosition, csv.options.to.encoding); |
| 357 | | } |
| 358 | 68 | if (!preserve) { |
| 359 | 68 | csv.state.countWriten++; |
| 360 | | } |
| 361 | 68 | return true; |
| 362 | | }; |
| 363 | 11 | error = function(e) { |
| 364 | 0 | csv.readable = false; |
| 365 | 0 | csv.writable = false; |
| 366 | 0 | csv.emit('error', e); |
| 367 | 0 | if (csv.readStream) { |
| 368 | 0 | csv.readStream.destroy(); |
| 369 | | } |
| 370 | 0 | return e; |
| 371 | | }; |
| 372 | 11 | return csv; |
| 373 | | }; |