-
Notifications
You must be signed in to change notification settings - Fork 42
/
csv.js
171 lines (152 loc) · 5.63 KB
/
csv.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/* jshint node:true */
'use strict';
/**
* Module dependencies.
*/
const joinRows = require('../core/join-rows');
const Handler = require('./handler');
const helper = require('../core/helper');
class Parser {
constructor(options) {
this._options = options || {};
this._handler = new Handler(this._options);
this._headers = this._options.headers || [];
this._escape = require('../core/escape-delimiters')(
this._options.textDelimiter,
this._options.rowDelimiter,
this._options.forceTextDelimiter
);
}
/**
* Generates a CSV file with optional headers based on the passed JSON,
* with can be an Object or Array.
*
* @param {Object|Array} json
* @param {Function} done(err,csv) - Callback function
* if error, returning error in call back.
* if csv is created successfully, returning csv output to callback.
*/
parse(json, done, stream) {
if (helper.isArray(json)) return done(null, this._parseArray(json, stream));
else if (helper.isObject(json)) return done(null, this._parseObject(json));
return done(new Error('Unable to parse the JSON object, its not an Array or Object.'));
}
get headers() {
let headers = this._headers;
if (this._options.rename && this._options.rename.length > 0)
headers = headers.map((header) => this._options.rename[this._options.headers.indexOf(header)] || header);
if (this._options.forceTextDelimiter) {
headers = headers.map((header) => {
return `${this._options.textDelimiter}${header}${this._options.textDelimiter}`;
});
}
if (this._options.mapHeaders)
headers = headers.map(this._options.mapHeaders);
return headers.join(this._options.rowDelimiter);
}
_checkRows(rows) {
let lastRow = null;
let finalRows = [];
let fillGaps = (col, index) => col === '' || col === undefined ? lastRow[index] : col;
for (let row of rows) {
let missing = this._headers.length - row.length;
if (missing > 0) row = row.concat(Array(missing).join(".").split("."));
if (lastRow && this._options.fillGaps) row = row.map(fillGaps);
finalRows.push(row.join(this._options.rowDelimiter));
lastRow = row;
}
return finalRows;
}
_parseArray(json, stream) {
let self = this;
this._headers = this._headers || [];
let fileRows = [];
let outputFile;
let fillRows;
let getHeaderIndex = function(header) {
var index = self._headers.indexOf(header);
if (index === -1) {
self._headers.push(header);
index = self._headers.indexOf(header);
}
return index;
};
//Generate the csv output
fillRows = function(result) {
const rows = [];
const fillAndPush = (row) => rows.push(row.map(col => col != null ? col : ''));
// initialize the array with empty strings to handle 'unpopular' headers
const newRow = () => new Array(self._headers.length).fill(null);
const emptyRowIndexByHeader = {};
let currentRow = newRow();
for (let element of result) {
let elementHeaderIndex = getHeaderIndex(element.item);
if (currentRow[elementHeaderIndex] != undefined) {
fillAndPush(currentRow);
currentRow = newRow();
}
emptyRowIndexByHeader[elementHeaderIndex] = emptyRowIndexByHeader[elementHeaderIndex] || 0;
// make sure there isn't a empty row for this header
if (self._options.fillTopRow && emptyRowIndexByHeader[elementHeaderIndex] < rows.length) {
rows[emptyRowIndexByHeader[elementHeaderIndex]][elementHeaderIndex] = self._escape(element.value);
emptyRowIndexByHeader[elementHeaderIndex] += 1;
continue;
}
currentRow[elementHeaderIndex] = self._escape(element.value);
emptyRowIndexByHeader[elementHeaderIndex] += 1;
}
// push last row
if (currentRow.length > 0) {
fillAndPush(currentRow);
}
fileRows = fileRows.concat(self._checkRows(rows));
};
for (let item of json) {
//Call checkType to list all items inside this object
//Items are returned as a object {item: 'Prop Value, Item Name', value: 'Prop Data Value'}
let itemResult = self._handler.check(item, self._options.mainPathItem, item, json);
fillRows(itemResult);
}
if (!stream && self._options.includeHeaders) {
//Add the headers to the first line
fileRows.unshift(this.headers);
}
return joinRows(fileRows, self._options.endOfLine);
}
_parseObject(json) {
let self = this;
let fileRows = [];
let parseResult = [];
let outputFile;
let fillRows;
let horizontalRows = [
[],
[]
];
fillRows = function(result) {
var value = result.value || result.value === 0 ? result.value.toString() : self._options.undefinedString;
value = self._escape(value);
//Type header;value
if (self._options.verticalOutput) {
var row = [result.item, value];
fileRows.push(row.join(self._options.rowDelimiter));
} else {
horizontalRows[0].push(result.item);
horizontalRows[1].push(value);
}
};
for (var prop in json) {
var prefix = "";
if (this._options.mainPathItem)
prefix = this._options.mainPathItem + this._options.headerPathString;
parseResult = this._handler.check(json[prop], prefix + prop, prop, json);
parseResult.forEach(fillRows);
}
if (!this._options.verticalOutput) {
fileRows.push(horizontalRows[0].join(this._options.rowDelimiter));
fileRows.push(horizontalRows[1].join(this._options.rowDelimiter));
}
return joinRows(fileRows, this._options.endOfLine);
}
}
module.exports = Parser;