@teselagen/file-utils 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/index.js +297 -0
  3. package/package.json +13 -0
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # file-utils
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build file-utils` to build the library.
8
+
9
+ ## Running unit tests
10
+
11
+ Run `nx test file-utils` to execute the unit tests via [Jest](https://jestjs.io).
package/index.js ADDED
@@ -0,0 +1,297 @@
1
+ // packages/file-utils/src/lib/file-utils.js
2
+ import { camelCase, flatMap, remove, startsWith, snakeCase } from "lodash";
3
+ import { loadAsync } from "jszip";
4
+ import Promise2 from "bluebird";
5
+ import { parse, unparse } from "papaparse";
6
+ var logDebug = (...args) => {
7
+ if (process.env.DEBUG_CSV_PARSING) {
8
+ console.log(...args);
9
+ }
10
+ };
11
+ var allowedCsvFileTypes = [".csv", ".txt", ".xlsx"];
12
+ var isZipFile = (file) => {
13
+ const type = file.mimetype || file.type;
14
+ return type === "application/zip" || type === "application/x-zip-compressed";
15
+ };
16
+ var getExt = (file) => file.name.split(".").pop();
17
+ var isExcelFile = (file) => getExt(file) === "xlsx";
18
+ var isCsvFile = (file) => getExt(file) === "csv";
19
+ var isTextFile = (file) => ["text", "txt"].includes(getExt(file));
20
+ var isCsvOrExcelFile = (file) => isCsvFile(file) || isExcelFile(file);
21
+ var extractZipFiles = async (allFiles) => {
22
+ if (!Array.isArray(allFiles))
23
+ allFiles = [allFiles];
24
+ allFiles = [...allFiles];
25
+ const zipFiles = remove(allFiles, isZipFile);
26
+ if (!zipFiles.length)
27
+ return allFiles;
28
+ const zipFilesArray = Array.isArray(zipFiles) ? zipFiles : [zipFiles];
29
+ const parsedZips = await Promise2.map(
30
+ zipFilesArray,
31
+ (file) => loadAsync(file instanceof Blob ? file : file.originFileObj)
32
+ );
33
+ const zippedFiles = flatMap(
34
+ parsedZips,
35
+ (zip) => Object.keys(zip.files).map((key) => zip.files[key])
36
+ );
37
+ const unzippedFiles = await Promise2.map(zippedFiles, (file) => {
38
+ return file.async("blob").then(function(fileData) {
39
+ const newFileObj = new File([fileData], file.name);
40
+ return {
41
+ name: file.name,
42
+ originFileObj: newFileObj,
43
+ originalFileObj: newFileObj
44
+ };
45
+ });
46
+ });
47
+ if (unzippedFiles.length) {
48
+ return allFiles.concat(
49
+ unzippedFiles.filter(
50
+ ({ name, originFileObj }) => !name.includes("__MACOSX") && !name.includes(".DS_Store") && originFileObj.size !== 0
51
+ )
52
+ );
53
+ } else {
54
+ return allFiles;
55
+ }
56
+ };
57
+ var defaultCsvParserOptions = {
58
+ header: true,
59
+ skipEmptyLines: "greedy",
60
+ trimHeaders: true
61
+ };
62
+ var setupCsvParserOptions = (parserOptions = {}) => {
63
+ const {
64
+ camelCaseHeaders = false,
65
+ lowerCaseHeaders = false,
66
+ ...rest
67
+ } = parserOptions;
68
+ const papaParseOpts = { ...rest };
69
+ if (camelCaseHeaders) {
70
+ logDebug("[CSV-PARSER] camelCasing headers");
71
+ papaParseOpts.transformHeader = (header) => {
72
+ let transHeader = header;
73
+ if (!startsWith(header.trim(), "ext-")) {
74
+ transHeader = camelCase(header);
75
+ }
76
+ if (transHeader) {
77
+ logDebug(
78
+ `[CSV-PARSER] Transformed header from: ${header} to: ${transHeader}`
79
+ );
80
+ transHeader = transHeader.trim();
81
+ } else {
82
+ logDebug(`[CSV-PARSER] Not transforming header: ${header}`);
83
+ }
84
+ return transHeader;
85
+ };
86
+ } else if (lowerCaseHeaders) {
87
+ papaParseOpts.transformHeader = (header) => {
88
+ let transHeader = header;
89
+ if (!startsWith(header, "ext-")) {
90
+ transHeader = header.toLowerCase();
91
+ }
92
+ if (transHeader) {
93
+ logDebug(
94
+ `[CSV-PARSER] Transformed header from: ${header} to: ${transHeader}`
95
+ );
96
+ transHeader = transHeader.trim();
97
+ } else {
98
+ logDebug(`[CSV-PARSER] Not transforming header: ${header}`);
99
+ }
100
+ return transHeader;
101
+ };
102
+ }
103
+ return papaParseOpts;
104
+ };
105
+ var normalizeCsvHeaderHelper = (h) => snakeCase(h.toUpperCase()).toUpperCase();
106
+ function normalizeCsvHeader(header) {
107
+ if (header.startsWith("ext-") || header.startsWith("EXT-")) {
108
+ return header;
109
+ }
110
+ return normalizeCsvHeaderHelper(header);
111
+ }
112
+ var parseCsvFile = (csvFile, parserOptions = {}) => {
113
+ return new Promise2((resolve, reject) => {
114
+ const opts = {
115
+ ...defaultCsvParserOptions,
116
+ ...setupCsvParserOptions(parserOptions),
117
+ complete: (results) => {
118
+ if (results && results.errors && results.errors.length) {
119
+ return reject("Error in csv: " + JSON.stringify(results.errors));
120
+ }
121
+ resolve(results);
122
+ },
123
+ error: (error) => {
124
+ reject(error);
125
+ }
126
+ };
127
+ logDebug(`[CSV-PARSER] parseCsvFile opts:`, opts);
128
+ parse(csvFile.originFileObj, opts);
129
+ });
130
+ };
131
+ var jsonToCsv = (jsonData, options = {}) => {
132
+ const csv = unparse(jsonData, options);
133
+ return csv;
134
+ };
135
+ var parseCsvString = (csvString, parserOptions = {}) => {
136
+ const opts = {
137
+ ...defaultCsvParserOptions,
138
+ ...setupCsvParserOptions(parserOptions)
139
+ };
140
+ logDebug(`[CSV-PARSER] parseCsvString opts:`, opts);
141
+ return parse(csvString, opts);
142
+ };
143
+ async function parseCsvOrExcelFile(fileOrFiles, { csvParserOptions } = {}) {
144
+ let csvFile, excelFile, txtFile;
145
+ if (Array.isArray(fileOrFiles)) {
146
+ csvFile = fileOrFiles.find(isCsvFile);
147
+ excelFile = fileOrFiles.find(isExcelFile);
148
+ txtFile = fileOrFiles.find(isTextFile);
149
+ } else {
150
+ if (isExcelFile(fileOrFiles))
151
+ excelFile = fileOrFiles;
152
+ else if (isCsvFile(fileOrFiles))
153
+ csvFile = fileOrFiles;
154
+ else if (isTextFile(fileOrFiles))
155
+ txtFile = fileOrFiles;
156
+ }
157
+ if (!csvFile && !excelFile && !txtFile) {
158
+ throw new Error("No csv or excel files found");
159
+ }
160
+ if (!csvFile && !excelFile)
161
+ csvFile = txtFile;
162
+ if (!csvFile && excelFile && window.parseExcelToCsv) {
163
+ csvFile = await window.parseExcelToCsv(
164
+ excelFile.originFileObj || excelFile
165
+ );
166
+ if (csvFile.error) {
167
+ throw new Error(csvFile.error);
168
+ }
169
+ } else if (excelFile) {
170
+ throw new Error("Excel Parser not initialized on the window");
171
+ }
172
+ const parsedCsv = await parseCsvFile(csvFile, csvParserOptions);
173
+ parsedCsv.originalFile = csvFile;
174
+ return parsedCsv;
175
+ }
176
+ var validateCSVRequiredHeaders = (fields, requiredHeaders, filename) => {
177
+ const missingRequiredHeaders = requiredHeaders.filter((field) => {
178
+ return !fields.includes(field);
179
+ });
180
+ if (missingRequiredHeaders.length) {
181
+ const name = filename ? `The file ${filename}` : "CSV file";
182
+ return `${name} is missing required headers. (${missingRequiredHeaders.join(
183
+ ", "
184
+ )})`;
185
+ }
186
+ };
187
+ var validateCSVRow = (row, requiredHeaders, index) => {
188
+ const missingRequiredFields = requiredHeaders.filter((field) => !row[field]);
189
+ if (missingRequiredFields.length) {
190
+ if (missingRequiredFields.length === 1) {
191
+ return `Row ${index + 1} is missing the required field "${missingRequiredFields[0]}"`;
192
+ } else {
193
+ return `Row ${index + 1} is missing these required fields: ${missingRequiredFields.join(
194
+ ", "
195
+ )}`;
196
+ }
197
+ }
198
+ };
199
+ var cleanCommaSeparatedCell = (cellData) => (cellData || "").split(",").map((n) => n.trim()).filter((n) => n);
200
+ var cleanCsvExport = (rows) => {
201
+ const allHeaders = [];
202
+ rows.forEach((row) => {
203
+ Object.keys(row).forEach((header) => {
204
+ if (!allHeaders.includes(header)) {
205
+ allHeaders.push(header);
206
+ }
207
+ });
208
+ });
209
+ rows.forEach((row) => {
210
+ allHeaders.forEach((header) => {
211
+ row[header] = row[header] || "";
212
+ });
213
+ });
214
+ return rows;
215
+ };
216
+ var filterFilesInZip = async (file, accepted) => {
217
+ const zipExtracted = await extractZipFiles(file);
218
+ const acceptedFiles = [];
219
+ for (const extFile of zipExtracted) {
220
+ const extension = "." + getExt(extFile);
221
+ if (accepted.some((ext) => ext === extension)) {
222
+ acceptedFiles.push(extFile);
223
+ }
224
+ }
225
+ if (acceptedFiles.length && acceptedFiles.length < zipExtracted.length)
226
+ window.toastr.warning("Some files don't have the proper file extension.");
227
+ if (!acceptedFiles.length)
228
+ window.toastr.warning("No files with the proper extension were found.");
229
+ return acceptedFiles;
230
+ };
231
+ function removeExt(filename) {
232
+ if (filename && filename.includes(".")) {
233
+ return filename.split(".").slice(0, -1).join(".");
234
+ } else {
235
+ return filename;
236
+ }
237
+ }
238
+ async function uploadAndProcessFiles(files = []) {
239
+ if (!files.length)
240
+ return null;
241
+ const formData = new FormData();
242
+ files.forEach(({ originFileObj }) => formData.append("file", originFileObj));
243
+ const response = await window.api.post("/user_uploads/", formData);
244
+ return response.data.map((d) => ({
245
+ encoding: d.encoding,
246
+ mimetype: d.mimetype,
247
+ originalname: d.originalname,
248
+ path: d.path,
249
+ size: d.size
250
+ }));
251
+ }
252
+ async function encodeFilesForRequest(files) {
253
+ const encodedFiles = [];
254
+ for (const file of files) {
255
+ const encoded = await fileToBase64(file.originalFileObj);
256
+ const data = encoded.split(",");
257
+ encodedFiles.push({
258
+ type: file.type,
259
+ base64Data: data[1],
260
+ name: file.name
261
+ });
262
+ }
263
+ return encodedFiles;
264
+ }
265
+ var fileToBase64 = (file) => {
266
+ return new Promise2((resolve) => {
267
+ const reader = new FileReader();
268
+ reader.onload = function(event) {
269
+ resolve(event.target.result);
270
+ };
271
+ reader.readAsDataURL(file);
272
+ });
273
+ };
274
+ export {
275
+ allowedCsvFileTypes,
276
+ cleanCommaSeparatedCell,
277
+ cleanCsvExport,
278
+ encodeFilesForRequest,
279
+ extractZipFiles,
280
+ filterFilesInZip,
281
+ getExt,
282
+ isCsvFile,
283
+ isCsvOrExcelFile,
284
+ isExcelFile,
285
+ isTextFile,
286
+ isZipFile,
287
+ jsonToCsv,
288
+ normalizeCsvHeader,
289
+ parseCsvFile,
290
+ parseCsvOrExcelFile,
291
+ parseCsvString,
292
+ removeExt,
293
+ setupCsvParserOptions,
294
+ uploadAndProcessFiles,
295
+ validateCSVRequiredHeaders,
296
+ validateCSVRow
297
+ };
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@teselagen/file-utils",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "bluebird": "^3.7.2",
7
+ "jszip": "^3.10.1",
8
+ "lodash": "^4.17.21",
9
+ "papaparse": "^5.4.1"
10
+ },
11
+ "module": "index.js",
12
+ "main": "index.js"
13
+ }