1
|
|
|
// Generated by CoffeeScript 2.0.1 |
2
|
|
|
// # CSV Stringifier |
3
|
|
|
|
4
|
|
|
// Please look at the [README], the [samples] and the [tests] for additional |
5
|
|
|
// information. |
6
|
|
|
var Stringifier, get, stream, util; |
7
|
|
|
|
8
|
|
|
stream = require('stream'); |
9
|
|
|
|
10
|
|
|
util = require('util'); |
11
|
|
|
|
12
|
|
|
get = require('lodash.get'); |
13
|
|
|
|
14
|
|
|
// ## Usage |
15
|
|
|
|
16
|
|
|
// This module export a function as its main entry point and return a transform |
17
|
|
|
// stream. |
18
|
|
|
|
19
|
|
|
// Refers to the [official prject documentation](http://csv.adaltas.com/stringify/) |
20
|
|
|
// on how to call this function. |
21
|
|
|
module.exports = function() { |
22
|
|
|
var callback, chunks, data, options, stringifier; |
23
|
|
|
if (arguments.length === 3) { |
24
|
|
|
data = arguments[0]; |
25
|
|
|
options = arguments[1]; |
26
|
|
|
callback = arguments[2]; |
27
|
|
|
} else if (arguments.length === 2) { |
28
|
|
|
if (Array.isArray(arguments[0])) { |
29
|
|
|
data = arguments[0]; |
30
|
|
|
} else { |
31
|
|
|
options = arguments[0]; |
32
|
|
|
} |
33
|
|
|
if (typeof arguments[1] === 'function') { |
34
|
|
|
callback = arguments[1]; |
35
|
|
|
} else { |
36
|
|
|
options = arguments[1]; |
37
|
|
|
} |
38
|
|
|
} else if (arguments.length === 1) { |
39
|
|
|
if (typeof arguments[0] === 'function') { |
40
|
|
|
callback = arguments[0]; |
41
|
|
|
} else if (Array.isArray(arguments[0])) { |
42
|
|
|
data = arguments[0]; |
43
|
|
|
} else { |
44
|
|
|
options = arguments[0]; |
45
|
|
|
} |
46
|
|
|
} |
47
|
|
|
if (options == null) { |
|
|
|
|
48
|
|
|
options = {}; |
49
|
|
|
} |
50
|
|
|
stringifier = new Stringifier(options); |
51
|
|
|
if (data) { |
52
|
|
|
process.nextTick(function() { |
53
|
|
|
var d, j, len; |
54
|
|
|
for (j = 0, len = data.length; j < len; j++) { |
55
|
|
|
d = data[j]; |
56
|
|
|
stringifier.write(d); |
57
|
|
|
} |
58
|
|
|
return stringifier.end(); |
59
|
|
|
}); |
60
|
|
|
} |
61
|
|
|
if (callback) { |
62
|
|
|
chunks = []; |
63
|
|
|
stringifier.on('readable', function() { |
64
|
|
|
var chunk, results; |
65
|
|
|
results = []; |
66
|
|
|
while (chunk = stringifier.read()) { |
67
|
|
|
results.push(chunks.push(chunk)); |
68
|
|
|
} |
69
|
|
|
return results; |
70
|
|
|
}); |
71
|
|
|
stringifier.on('error', function(err) { |
72
|
|
|
return callback(err); |
73
|
|
|
}); |
74
|
|
|
stringifier.on('end', function() { |
75
|
|
|
return callback(null, chunks.join('')); |
76
|
|
|
}); |
77
|
|
|
} |
78
|
|
|
return stringifier; |
79
|
|
|
}; |
80
|
|
|
|
81
|
|
|
// You can also use *util.promisify* native function (Node.js 8+) in order to wrap callbacks into promises for more convenient use when source is a readable stream and you are OK with storing entire result set in memory: |
82
|
|
|
|
83
|
|
|
// ``` |
84
|
|
|
// const { promisify } = require('util'); |
85
|
|
|
// const csv = require('csv'); |
86
|
|
|
// const stringifyAsync = promisify(csv.stringify); |
87
|
|
|
|
88
|
|
|
// //returns promise |
89
|
|
|
// function generateCsv(sourceData) { |
90
|
|
|
// return stringifyAsync(sourceData); |
91
|
|
|
// } |
92
|
|
|
// ``` |
93
|
|
|
|
94
|
|
|
// ## `Stringifier([options])` |
95
|
|
|
|
96
|
|
|
// Options are documented [here](http://csv.adaltas.com/stringify/). |
97
|
|
|
Stringifier = function(opts = {}) { |
98
|
|
|
var base, base1, base10, base11, base12, base13, base2, base3, base4, base5, base6, base7, base8, base9, k, options, v; |
99
|
|
|
// Immutable options |
100
|
|
|
options = {}; |
101
|
|
|
for (k in opts) { |
102
|
|
|
v = opts[k]; |
103
|
|
|
options[k] = v; |
104
|
|
|
} |
105
|
|
|
stream.Transform.call(this, options); |
106
|
|
|
//# Default options |
107
|
|
|
this.options = options; |
108
|
|
|
if ((base = this.options).delimiter == null) { |
109
|
|
|
base.delimiter = ','; |
110
|
|
|
} |
111
|
|
|
if ((base1 = this.options).quote == null) { |
112
|
|
|
base1.quote = '"'; |
113
|
|
|
} |
114
|
|
|
if ((base2 = this.options).quoted == null) { |
115
|
|
|
base2.quoted = false; |
116
|
|
|
} |
117
|
|
|
if ((base3 = this.options).quotedEmpty == null) { |
118
|
|
|
base3.quotedEmpty = void 0; |
|
|
|
|
119
|
|
|
} |
120
|
|
|
if ((base4 = this.options).quotedString == null) { |
121
|
|
|
base4.quotedString = false; |
122
|
|
|
} |
123
|
|
|
if ((base5 = this.options).eof == null) { |
124
|
|
|
base5.eof = true; |
125
|
|
|
} |
126
|
|
|
if ((base6 = this.options).escape == null) { |
127
|
|
|
base6.escape = '"'; |
128
|
|
|
} |
129
|
|
|
if ((base7 = this.options).columns == null) { |
130
|
|
|
base7.columns = null; |
131
|
|
|
} |
132
|
|
|
if ((base8 = this.options).header == null) { |
133
|
|
|
base8.header = false; |
134
|
|
|
} |
135
|
|
|
if ((base9 = this.options).formatters == null) { |
136
|
|
|
base9.formatters = {}; |
137
|
|
|
} |
138
|
|
|
if ((base10 = this.options.formatters).date == null) { |
139
|
|
|
base10.date = function(value) { |
140
|
|
|
// Cast date to timestamp string by default |
141
|
|
|
return '' + value.getTime(); |
142
|
|
|
}; |
143
|
|
|
} |
144
|
|
|
if ((base11 = this.options.formatters).bool == null) { |
145
|
|
|
base11.bool = function(value) { |
146
|
|
|
// Cast boolean to string by default |
147
|
|
|
if (value) { |
148
|
|
|
return '1'; |
149
|
|
|
} else { |
150
|
|
|
return ''; |
151
|
|
|
} |
152
|
|
|
}; |
153
|
|
|
} |
154
|
|
|
if ((base12 = this.options.formatters).object == null) { |
155
|
|
|
base12.object = function(value) { |
156
|
|
|
// Stringify object as JSON by default |
157
|
|
|
return JSON.stringify(value); |
158
|
|
|
}; |
159
|
|
|
} |
160
|
|
|
if ((base13 = this.options).rowDelimiter == null) { |
161
|
|
|
base13.rowDelimiter = '\n'; |
162
|
|
|
} |
163
|
|
|
// Internal usage, state related |
164
|
|
|
if (this.countWriten == null) { |
165
|
|
|
this.countWriten = 0; |
166
|
|
|
} |
167
|
|
|
switch (this.options.rowDelimiter) { |
|
|
|
|
168
|
|
|
case 'auto': |
169
|
|
|
this.options.rowDelimiter = null; |
170
|
|
|
break; |
171
|
|
|
case 'unix': |
172
|
|
|
this.options.rowDelimiter = "\n"; |
173
|
|
|
break; |
174
|
|
|
case 'mac': |
175
|
|
|
this.options.rowDelimiter = "\r"; |
176
|
|
|
break; |
177
|
|
|
case 'windows': |
178
|
|
|
this.options.rowDelimiter = "\r\n"; |
179
|
|
|
break; |
180
|
|
|
case 'unicode': |
181
|
|
|
this.options.rowDelimiter = "\u2028"; |
182
|
|
|
} |
183
|
|
|
return this; |
184
|
|
|
}; |
185
|
|
|
|
186
|
|
|
util.inherits(Stringifier, stream.Transform); |
187
|
|
|
|
188
|
|
|
module.exports.Stringifier = Stringifier; |
189
|
|
|
|
190
|
|
|
// ## `Stringifier.prototype.headers` |
191
|
|
|
|
192
|
|
|
// Print the header line if the option "header" is "true". |
193
|
|
|
Stringifier.prototype.headers = function() { |
194
|
|
|
var k, label, labels; |
195
|
|
|
if (!this.options.header) { |
196
|
|
|
return; |
197
|
|
|
} |
198
|
|
|
if (!this.options.columns) { |
199
|
|
|
return; |
200
|
|
|
} |
201
|
|
|
labels = this.options.columns; |
202
|
|
|
// If columns is an object, keys are fields and values are labels |
203
|
|
|
if (typeof labels === 'object') { |
204
|
|
|
labels = (function() { |
205
|
|
|
var results; |
206
|
|
|
results = []; |
207
|
|
|
for (k in labels) { |
208
|
|
|
label = labels[k]; |
|
|
|
|
209
|
|
|
results.push(label); |
|
|
|
|
210
|
|
|
} |
211
|
|
|
return results; |
212
|
|
|
})(); |
213
|
|
|
} |
214
|
|
|
if (this.options.eof) { |
215
|
|
|
labels = this.stringify(labels) + this.options.rowDelimiter; |
216
|
|
|
} else { |
217
|
|
|
labels = this.stringify(labels); |
218
|
|
|
} |
219
|
|
|
return stream.Transform.prototype.write.call(this, labels); |
220
|
|
|
}; |
221
|
|
|
|
222
|
|
|
Stringifier.prototype.end = function(chunk, encoding, callback) { |
223
|
|
|
if (this.countWriten === 0) { |
224
|
|
|
this.headers(); |
225
|
|
|
} |
226
|
|
|
return stream.Transform.prototype.end.apply(this, arguments); |
227
|
|
|
}; |
228
|
|
|
|
229
|
|
|
Stringifier.prototype.write = function(chunk, encoding, callback) { |
230
|
|
|
var base, e, preserve; |
231
|
|
|
if (chunk == null) { |
232
|
|
|
return; |
233
|
|
|
} |
234
|
|
|
preserve = typeof chunk !== 'object'; |
235
|
|
|
// Emit and stringify the record |
236
|
|
|
if (!preserve) { |
237
|
|
|
if (this.countWriten === 0 && !Array.isArray(chunk)) { |
238
|
|
|
if ((base = this.options).columns == null) { |
239
|
|
|
base.columns = Object.keys(chunk); |
240
|
|
|
} |
241
|
|
|
} |
242
|
|
|
try { |
243
|
|
|
this.emit('record', chunk, this.countWriten); |
244
|
|
|
} catch (error) { |
245
|
|
|
e = error; |
246
|
|
|
return this.emit('error', e); |
247
|
|
|
} |
248
|
|
|
// Convert the record into a string |
249
|
|
|
if (this.options.eof) { |
250
|
|
|
chunk = this.stringify(chunk) + this.options.rowDelimiter; |
251
|
|
|
} else { |
252
|
|
|
chunk = this.stringify(chunk); |
253
|
|
|
if (this.options.header || this.countWriten) { |
254
|
|
|
chunk = this.options.rowDelimiter + chunk; |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
if (typeof chunk === 'number') { |
259
|
|
|
// Emit the csv |
260
|
|
|
chunk = `${chunk}`; |
261
|
|
|
} |
262
|
|
|
if (this.countWriten === 0) { |
263
|
|
|
this.headers(); |
264
|
|
|
} |
265
|
|
|
if (!preserve) { |
266
|
|
|
this.countWriten++; |
267
|
|
|
} |
268
|
|
|
return stream.Transform.prototype.write.call(this, chunk, encoding, callback); |
269
|
|
|
}; |
270
|
|
|
|
271
|
|
|
// ## `Stringifier.prototype._transform(line)` |
272
|
|
|
Stringifier.prototype._transform = function(chunk, encoding, callback) { |
273
|
|
|
this.push(chunk); |
274
|
|
|
return callback(); |
275
|
|
|
}; |
276
|
|
|
|
277
|
|
|
// ## `Stringifier.prototype.stringify(line)` |
278
|
|
|
|
279
|
|
|
// Convert a line to a string. Line may be an object, an array or a string. |
280
|
|
|
Stringifier.prototype.stringify = function(line) { |
281
|
|
|
var _line, column, columns, containsEscape, containsLinebreak, containsQuote, containsdelimiter, delimiter, escape, field, i, j, l, newLine, quote, ref, ref1, regexp, shouldQuote, value; |
282
|
|
|
if (typeof line !== 'object') { |
283
|
|
|
return line; |
284
|
|
|
} |
285
|
|
|
columns = this.options.columns; |
286
|
|
|
if (typeof columns === 'object' && columns !== null && !Array.isArray(columns)) { |
287
|
|
|
columns = Object.keys(columns); |
288
|
|
|
} |
289
|
|
|
delimiter = this.options.delimiter; |
290
|
|
|
quote = this.options.quote; |
291
|
|
|
escape = this.options.escape; |
292
|
|
|
if (!Array.isArray(line)) { |
293
|
|
|
_line = []; |
294
|
|
|
if (columns) { |
295
|
|
|
for (i = j = 0, ref = columns.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { |
296
|
|
|
column = columns[i]; |
297
|
|
|
value = get(line, column); |
298
|
|
|
_line[i] = (typeof value === 'undefined' || value === null) ? '' : value; |
299
|
|
|
} |
300
|
|
|
} else { |
301
|
|
|
for (column in line) { |
302
|
|
|
_line.push(line[column]); |
303
|
|
|
} |
304
|
|
|
} |
305
|
|
|
line = _line; |
306
|
|
|
_line = null; |
|
|
|
|
307
|
|
|
} else if (columns) { // Note, we used to have @options.columns |
308
|
|
|
// We are getting an array but the user want specified output columns. In |
309
|
|
|
// this case, we respect the columns indexes |
310
|
|
|
line.splice(columns.length); |
311
|
|
|
} |
312
|
|
|
if (Array.isArray(line)) { |
313
|
|
|
newLine = ''; |
314
|
|
|
for (i = l = 0, ref1 = line.length; 0 <= ref1 ? l < ref1 : l > ref1; i = 0 <= ref1 ? ++l : --l) { |
315
|
|
|
field = line[i]; |
316
|
|
|
if (typeof field === 'string') { |
317
|
|
|
|
318
|
|
|
// fine 99% of the cases, keep going |
319
|
|
|
} else if (typeof field === 'number') { |
320
|
|
|
// Cast number to string |
321
|
|
|
field = '' + field; |
322
|
|
|
} else if (typeof field === 'boolean') { |
323
|
|
|
field = this.options.formatters.bool(field); |
324
|
|
|
} else if (field instanceof Date) { |
325
|
|
|
field = this.options.formatters.date(field); |
326
|
|
|
} else if (typeof field === 'object' && field !== null) { |
327
|
|
|
field = this.options.formatters.object(field); |
328
|
|
|
} |
329
|
|
|
if (field) { |
330
|
|
|
if (typeof field !== 'string') { |
331
|
|
|
return this.emit('error', Error('Formatter must return a string, null or undefined')); |
332
|
|
|
} |
333
|
|
|
containsdelimiter = field.indexOf(delimiter) >= 0; |
334
|
|
|
containsQuote = field.indexOf(quote) >= 0; |
335
|
|
|
containsEscape = field.indexOf(escape) >= 0 && (escape !== quote); |
336
|
|
|
containsLinebreak = field.indexOf('\r') >= 0 || field.indexOf('\n') >= 0; |
337
|
|
|
shouldQuote = containsQuote || containsdelimiter || containsLinebreak || this.options.quoted || (this.options.quotedString && typeof line[i] === 'string'); |
338
|
|
|
if (shouldQuote && containsEscape) { |
339
|
|
|
regexp = escape === '\\' ? new RegExp(escape + escape, 'g') : new RegExp(escape, 'g'); |
340
|
|
|
field = field.replace(regexp, escape + escape); |
341
|
|
|
} |
342
|
|
|
if (containsQuote) { |
343
|
|
|
regexp = new RegExp(quote, 'g'); |
344
|
|
|
field = field.replace(regexp, escape + quote); |
345
|
|
|
} |
346
|
|
|
if (shouldQuote) { |
347
|
|
|
field = quote + field + quote; |
348
|
|
|
} |
349
|
|
|
newLine += field; |
350
|
|
|
} else if (this.options.quotedEmpty || ((this.options.quotedEmpty == null) && line[i] === '' && this.options.quotedString)) { |
351
|
|
|
newLine += quote + quote; |
352
|
|
|
} |
353
|
|
|
if (i !== line.length - 1) { |
354
|
|
|
newLine += delimiter; |
355
|
|
|
} |
356
|
|
|
} |
357
|
|
|
line = newLine; |
358
|
|
|
} |
359
|
|
|
return line; |
360
|
|
|
}; |
361
|
|
|
|
362
|
|
|
// [readme]: https://github.com/wdavidw/node-csv-stringify |
363
|
|
|
// [samples]: https://github.com/wdavidw/node-csv-stringify/tree/master/samples |
364
|
|
|
// [tests]: https://github.com/wdavidw/node-csv-stringify/tree/master/test |
365
|
|
|
|