csv.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. const fs = require('fs');
  2. const fastCsv = require('fast-csv');
  3. const customParseFormat = require('dayjs/plugin/customParseFormat');
  4. const utc = require('dayjs/plugin/utc');
  5. const dayjs = require('dayjs').extend(customParseFormat).extend(utc);
  6. const StreamBuf = require('../utils/stream-buf');
  7. const {
  8. fs: {exists},
  9. } = require('../utils/utils');
  10. /* eslint-disable quote-props */
  11. const SpecialValues = {
  12. true: true,
  13. false: false,
  14. '#N/A': {error: '#N/A'},
  15. '#REF!': {error: '#REF!'},
  16. '#NAME?': {error: '#NAME?'},
  17. '#DIV/0!': {error: '#DIV/0!'},
  18. '#NULL!': {error: '#NULL!'},
  19. '#VALUE!': {error: '#VALUE!'},
  20. '#NUM!': {error: '#NUM!'},
  21. };
  22. /* eslint-ensable quote-props */
  23. class CSV {
  24. constructor(workbook) {
  25. this.workbook = workbook;
  26. this.worksheet = null;
  27. }
  28. async readFile(filename, options) {
  29. options = options || {};
  30. if (!(await exists(filename))) {
  31. throw new Error(`File not found: ${filename}`);
  32. }
  33. const stream = fs.createReadStream(filename);
  34. const worksheet = await this.read(stream, options);
  35. stream.close();
  36. return worksheet;
  37. }
  38. read(stream, options) {
  39. options = options || {};
  40. return new Promise((resolve, reject) => {
  41. const worksheet = this.workbook.addWorksheet(options.sheetName);
  42. const dateFormats = options.dateFormats || [
  43. 'YYYY-MM-DD[T]HH:mm:ssZ',
  44. 'YYYY-MM-DD[T]HH:mm:ss',
  45. 'MM-DD-YYYY',
  46. 'YYYY-MM-DD',
  47. ];
  48. const map =
  49. options.map ||
  50. function(datum) {
  51. if (datum === '') {
  52. return null;
  53. }
  54. const datumNumber = Number(datum);
  55. if (!Number.isNaN(datumNumber) && datumNumber !== Infinity) {
  56. return datumNumber;
  57. }
  58. const dt = dateFormats.reduce((matchingDate, currentDateFormat) => {
  59. if (matchingDate) {
  60. return matchingDate;
  61. }
  62. const dayjsObj = dayjs(datum, currentDateFormat, true);
  63. if (dayjsObj.isValid()) {
  64. return dayjsObj;
  65. }
  66. return null;
  67. }, null);
  68. if (dt) {
  69. return new Date(dt.valueOf());
  70. }
  71. const special = SpecialValues[datum];
  72. if (special !== undefined) {
  73. return special;
  74. }
  75. return datum;
  76. };
  77. const csvStream = fastCsv
  78. .parse(options.parserOptions)
  79. .on('data', data => {
  80. worksheet.addRow(data.map(map));
  81. })
  82. .on('end', () => {
  83. csvStream.emit('worksheet', worksheet);
  84. });
  85. csvStream.on('worksheet', resolve).on('error', reject);
  86. stream.pipe(csvStream);
  87. });
  88. }
  89. /**
  90. * @deprecated since version 4.0. You should use `CSV#read` instead. Please follow upgrade instruction: https://github.com/exceljs/exceljs/blob/master/UPGRADE-4.0.md
  91. */
  92. createInputStream() {
  93. throw new Error(
  94. '`CSV#createInputStream` is deprecated. You should use `CSV#read` instead. This method will be removed in version 5.0. Please follow upgrade instruction: https://github.com/exceljs/exceljs/blob/master/UPGRADE-4.0.md'
  95. );
  96. }
  97. write(stream, options) {
  98. return new Promise((resolve, reject) => {
  99. options = options || {};
  100. // const encoding = options.encoding || 'utf8';
  101. // const separator = options.separator || ',';
  102. // const quoteChar = options.quoteChar || '\'';
  103. const worksheet = this.workbook.getWorksheet(options.sheetName || options.sheetId);
  104. const csvStream = fastCsv.format(options.formatterOptions);
  105. stream.on('finish', () => {
  106. resolve();
  107. });
  108. csvStream.on('error', reject);
  109. csvStream.pipe(stream);
  110. const {dateFormat, dateUTC} = options;
  111. const map =
  112. options.map ||
  113. (value => {
  114. if (value) {
  115. if (value.text || value.hyperlink) {
  116. return value.hyperlink || value.text || '';
  117. }
  118. if (value.formula || value.result) {
  119. return value.result || '';
  120. }
  121. if (value instanceof Date) {
  122. if (dateFormat) {
  123. return dateUTC
  124. ? dayjs.utc(value).format(dateFormat)
  125. : dayjs(value).format(dateFormat);
  126. }
  127. return dateUTC ? dayjs.utc(value).format() : dayjs(value).format();
  128. }
  129. if (value.error) {
  130. return value.error;
  131. }
  132. if (typeof value === 'object') {
  133. return JSON.stringify(value);
  134. }
  135. }
  136. return value;
  137. });
  138. const includeEmptyRows = options.includeEmptyRows === undefined || options.includeEmptyRows;
  139. let lastRow = 1;
  140. if (worksheet) {
  141. worksheet.eachRow((row, rowNumber) => {
  142. if (includeEmptyRows) {
  143. while (lastRow++ < rowNumber - 1) {
  144. csvStream.write([]);
  145. }
  146. }
  147. const {values} = row;
  148. values.shift();
  149. csvStream.write(values.map(map));
  150. lastRow = rowNumber;
  151. });
  152. }
  153. csvStream.end();
  154. });
  155. }
  156. writeFile(filename, options) {
  157. options = options || {};
  158. const streamOptions = {
  159. encoding: options.encoding || 'utf8',
  160. };
  161. const stream = fs.createWriteStream(filename, streamOptions);
  162. return this.write(stream, options);
  163. }
  164. async writeBuffer(options) {
  165. const stream = new StreamBuf();
  166. await this.write(stream, options);
  167. return stream.read();
  168. }
  169. }
  170. module.exports = CSV;