read-excel-file 9.3.4 → 9.3.5

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 (251) hide show
  1. package/CHANGELOG.md +6 -2
  2. package/README.md +32 -2
  3. package/browser/index.cjs +3 -0
  4. package/browser/index.d.ts +16 -0
  5. package/browser/index.js +4 -1
  6. package/bundle/read-excel-file.min.js +1 -1
  7. package/bundle/read-excel-file.min.js.map +1 -1
  8. package/commonjs/export/convertInputToNodeStream.js +0 -12
  9. package/commonjs/export/convertInputToNodeStream.js.map +1 -1
  10. package/commonjs/export/parseSheet.js +14 -7
  11. package/commonjs/export/parseSheet.js.map +1 -1
  12. package/commonjs/export/readSheetBrowser.js +3 -6
  13. package/commonjs/export/readSheetBrowser.js.map +1 -1
  14. package/commonjs/export/readSheetNode.js +3 -6
  15. package/commonjs/export/readSheetNode.js.map +1 -1
  16. package/commonjs/export/readSheetUniversal.js +2 -2
  17. package/commonjs/export/readSheetUniversal.js.map +1 -1
  18. package/commonjs/export/readSheetWebWorker.js +5 -5
  19. package/commonjs/export/readSheetWebWorker.js.map +1 -1
  20. package/commonjs/export/readXlsxFileBrowser.js +3 -6
  21. package/commonjs/export/readXlsxFileBrowser.js.map +1 -1
  22. package/commonjs/export/readXlsxFileNode.js +3 -6
  23. package/commonjs/export/readXlsxFileNode.js.map +1 -1
  24. package/commonjs/export/readXlsxFileUniversal.js +2 -2
  25. package/commonjs/export/readXlsxFileUniversal.js.map +1 -1
  26. package/commonjs/export/readXlsxFileWebWorker.js +5 -5
  27. package/commonjs/export/readXlsxFileWebWorker.js.map +1 -1
  28. package/commonjs/export/unpackXlsxFileBrowser.js +15 -4
  29. package/commonjs/export/unpackXlsxFileBrowser.js.map +1 -1
  30. package/commonjs/export/unpackXlsxFileBrowser.test.js.map +1 -0
  31. package/commonjs/export/unpackXlsxFileNode.js +32 -5
  32. package/commonjs/export/unpackXlsxFileNode.js.map +1 -1
  33. package/commonjs/export/unpackXlsxFileNode.test.js.map +1 -0
  34. package/commonjs/export/unpackXlsxFileUniversal.js +14 -3
  35. package/commonjs/export/unpackXlsxFileUniversal.js.map +1 -1
  36. package/commonjs/export/unpackXlsxFileUniversal.test.js.map +1 -0
  37. package/commonjs/parseSheetData/parseSheetData.js +9 -6
  38. package/commonjs/parseSheetData/parseSheetData.js.map +1 -1
  39. package/commonjs/utility/convertValuesFromUint8ArraysToStrings.js +100 -0
  40. package/commonjs/utility/convertValuesFromUint8ArraysToStrings.js.map +1 -0
  41. package/commonjs/xlsx/InvalidSpreadsheetError.js +55 -0
  42. package/commonjs/xlsx/InvalidSpreadsheetError.js.map +1 -0
  43. package/commonjs/xlsx/SheetNotFoundError.js +55 -0
  44. package/commonjs/xlsx/SheetNotFoundError.js.map +1 -0
  45. package/commonjs/xlsx/file/InputValidationStream.js +101 -0
  46. package/commonjs/xlsx/file/InputValidationStream.js.map +1 -0
  47. package/commonjs/xlsx/file/InputValidationStream.test.js.map +1 -0
  48. package/commonjs/xlsx/file/InvalidInputError.js +105 -0
  49. package/commonjs/xlsx/file/InvalidInputError.js.map +1 -0
  50. package/commonjs/xlsx/file/createFileTypeDetector.js +70 -0
  51. package/commonjs/xlsx/file/createFileTypeDetector.js.map +1 -0
  52. package/commonjs/xlsx/file/createFileTypeDetector.test.js.map +1 -0
  53. package/commonjs/xlsx/file/validateLeadingBytes.js +47 -0
  54. package/commonjs/xlsx/file/validateLeadingBytes.js.map +1 -0
  55. package/commonjs/xlsx/file/validateLeadingBytes.test.js.map +1 -0
  56. package/commonjs/xlsx/isDateFormat.js +32 -27
  57. package/commonjs/xlsx/isDateFormat.js.map +1 -1
  58. package/commonjs/xlsx/isDateFormatStyle.js +19 -21
  59. package/commonjs/xlsx/isDateFormatStyle.js.map +1 -1
  60. package/commonjs/xlsx/parseCell.js +252 -115
  61. package/commonjs/xlsx/parseCell.js.map +1 -1
  62. package/commonjs/xlsx/parseCellAddress.js +22 -18
  63. package/commonjs/xlsx/parseCellAddress.js.map +1 -1
  64. package/commonjs/xlsx/parseExcelDate.js +4 -124
  65. package/commonjs/xlsx/parseExcelDate.js.map +1 -1
  66. package/commonjs/xlsx/parseExcelDate.test.js.map +1 -1
  67. package/commonjs/xlsx/parseExcelTimestamp.js +139 -0
  68. package/commonjs/xlsx/parseExcelTimestamp.js.map +1 -0
  69. package/commonjs/xlsx/parseFilePaths.js +48 -47
  70. package/commonjs/xlsx/parseFilePaths.js.map +1 -1
  71. package/commonjs/xlsx/parseSharedStrings.js +91 -36
  72. package/commonjs/xlsx/parseSharedStrings.js.map +1 -1
  73. package/commonjs/xlsx/parseSheet.js +527 -69
  74. package/commonjs/xlsx/parseSheet.js.map +1 -1
  75. package/commonjs/xlsx/parseSpreadsheetContents.js +154 -83
  76. package/commonjs/xlsx/parseSpreadsheetContents.js.map +1 -1
  77. package/commonjs/xlsx/parseSpreadsheetInfo.js +60 -59
  78. package/commonjs/xlsx/parseSpreadsheetInfo.js.map +1 -1
  79. package/commonjs/xlsx/parseStyles.js +75 -75
  80. package/commonjs/xlsx/parseStyles.js.map +1 -1
  81. package/commonjs/xml/parseXml.js +112 -0
  82. package/commonjs/xml/parseXml.js.map +1 -0
  83. package/commonjs/xml/parseXml.test.js.map +1 -0
  84. package/commonjs/xml/parseXmlStream.js.map +1 -1
  85. package/commonjs/xml/parseXmlStream.sax.js +69 -28
  86. package/commonjs/xml/parseXmlStream.sax.js.map +1 -1
  87. package/commonjs/xml/parseXmlStream.saxen.js +66 -40
  88. package/commonjs/xml/parseXmlStream.saxen.js.map +1 -1
  89. package/commonjs/zip/UnzipError.js +55 -0
  90. package/commonjs/zip/UnzipError.js.map +1 -0
  91. package/commonjs/zip/unzipFromArrayBuffer.js +21 -1
  92. package/commonjs/zip/unzipFromArrayBuffer.js.map +1 -1
  93. package/commonjs/zip/unzipFromArrayBuffer.test.js.map +1 -0
  94. package/commonjs/zip/unzipFromStream.fflate.js +43 -35
  95. package/commonjs/zip/unzipFromStream.fflate.js.map +1 -1
  96. package/commonjs/zip/unzipFromStream.js +188 -6
  97. package/commonjs/zip/unzipFromStream.js.map +1 -1
  98. package/commonjs/zip/unzipFromStream.unzipper.js +19 -34
  99. package/commonjs/zip/unzipFromStream.unzipper.js.map +1 -1
  100. package/modules/export/convertInputToNodeStream.js +0 -12
  101. package/modules/export/convertInputToNodeStream.js.map +1 -1
  102. package/modules/export/parseSheet.js +13 -6
  103. package/modules/export/parseSheet.js.map +1 -1
  104. package/modules/export/readSheetBrowser.js +3 -5
  105. package/modules/export/readSheetBrowser.js.map +1 -1
  106. package/modules/export/readSheetNode.js +3 -5
  107. package/modules/export/readSheetNode.js.map +1 -1
  108. package/modules/export/readSheetUniversal.js +2 -2
  109. package/modules/export/readSheetUniversal.js.map +1 -1
  110. package/modules/export/readSheetWebWorker.js +7 -8
  111. package/modules/export/readSheetWebWorker.js.map +1 -1
  112. package/modules/export/readXlsxFileBrowser.js +3 -5
  113. package/modules/export/readXlsxFileBrowser.js.map +1 -1
  114. package/modules/export/readXlsxFileNode.js +3 -5
  115. package/modules/export/readXlsxFileNode.js.map +1 -1
  116. package/modules/export/readXlsxFileUniversal.js +2 -2
  117. package/modules/export/readXlsxFileUniversal.js.map +1 -1
  118. package/modules/export/readXlsxFileWebWorker.js +7 -8
  119. package/modules/export/readXlsxFileWebWorker.js.map +1 -1
  120. package/modules/export/unpackXlsxFileBrowser.js +15 -4
  121. package/modules/export/unpackXlsxFileBrowser.js.map +1 -1
  122. package/modules/export/unpackXlsxFileBrowser.test.js.map +1 -0
  123. package/modules/export/unpackXlsxFileNode.js +32 -5
  124. package/modules/export/unpackXlsxFileNode.js.map +1 -1
  125. package/modules/export/unpackXlsxFileNode.test.js.map +1 -0
  126. package/modules/export/unpackXlsxFileUniversal.js +14 -3
  127. package/modules/export/unpackXlsxFileUniversal.js.map +1 -1
  128. package/modules/export/unpackXlsxFileUniversal.test.js.map +1 -0
  129. package/modules/parseSheetData/parseSheetData.js +9 -6
  130. package/modules/parseSheetData/parseSheetData.js.map +1 -1
  131. package/modules/utility/convertValuesFromUint8ArraysToStrings.js +92 -0
  132. package/modules/utility/convertValuesFromUint8ArraysToStrings.js.map +1 -0
  133. package/modules/xlsx/InvalidSpreadsheetError.js +50 -0
  134. package/modules/xlsx/InvalidSpreadsheetError.js.map +1 -0
  135. package/modules/xlsx/SheetNotFoundError.js +50 -0
  136. package/modules/xlsx/SheetNotFoundError.js.map +1 -0
  137. package/modules/xlsx/file/InputValidationStream.js +96 -0
  138. package/modules/xlsx/file/InputValidationStream.js.map +1 -0
  139. package/modules/xlsx/file/InputValidationStream.test.js.map +1 -0
  140. package/modules/xlsx/file/InvalidInputError.js +100 -0
  141. package/modules/xlsx/file/InvalidInputError.js.map +1 -0
  142. package/modules/xlsx/file/createFileTypeDetector.js +63 -0
  143. package/modules/xlsx/file/createFileTypeDetector.js.map +1 -0
  144. package/modules/xlsx/file/createFileTypeDetector.test.js.map +1 -0
  145. package/modules/xlsx/file/validateLeadingBytes.js +37 -0
  146. package/modules/xlsx/file/validateLeadingBytes.js.map +1 -0
  147. package/modules/xlsx/file/validateLeadingBytes.test.js.map +1 -0
  148. package/modules/xlsx/isDateFormat.js +31 -27
  149. package/modules/xlsx/isDateFormat.js.map +1 -1
  150. package/modules/xlsx/isDateFormatStyle.js +18 -21
  151. package/modules/xlsx/isDateFormatStyle.js.map +1 -1
  152. package/modules/xlsx/parseCell.js +251 -111
  153. package/modules/xlsx/parseCell.js.map +1 -1
  154. package/modules/xlsx/parseCellAddress.js +21 -18
  155. package/modules/xlsx/parseCellAddress.js.map +1 -1
  156. package/modules/xlsx/parseExcelDate.js +4 -124
  157. package/modules/xlsx/parseExcelDate.js.map +1 -1
  158. package/modules/xlsx/parseExcelDate.test.js.map +1 -1
  159. package/modules/xlsx/parseExcelTimestamp.js +133 -0
  160. package/modules/xlsx/parseExcelTimestamp.js.map +1 -0
  161. package/modules/xlsx/parseFilePaths.js +48 -47
  162. package/modules/xlsx/parseFilePaths.js.map +1 -1
  163. package/modules/xlsx/parseSharedStrings.js +91 -37
  164. package/modules/xlsx/parseSharedStrings.js.map +1 -1
  165. package/modules/xlsx/parseSheet.js +526 -68
  166. package/modules/xlsx/parseSheet.js.map +1 -1
  167. package/modules/xlsx/parseSpreadsheetContents.js +153 -81
  168. package/modules/xlsx/parseSpreadsheetContents.js.map +1 -1
  169. package/modules/xlsx/parseSpreadsheetInfo.js +60 -59
  170. package/modules/xlsx/parseSpreadsheetInfo.js.map +1 -1
  171. package/modules/xlsx/parseStyles.js +75 -75
  172. package/modules/xlsx/parseStyles.js.map +1 -1
  173. package/modules/xml/parseXml.js +106 -0
  174. package/modules/xml/parseXml.js.map +1 -0
  175. package/modules/xml/parseXml.test.js.map +1 -0
  176. package/modules/xml/parseXmlStream.js +2 -0
  177. package/modules/xml/parseXmlStream.js.map +1 -1
  178. package/modules/xml/parseXmlStream.sax.js +69 -28
  179. package/modules/xml/parseXmlStream.sax.js.map +1 -1
  180. package/modules/xml/parseXmlStream.saxen.js +67 -40
  181. package/modules/xml/parseXmlStream.saxen.js.map +1 -1
  182. package/modules/zip/UnzipError.js +49 -0
  183. package/modules/zip/UnzipError.js.map +1 -0
  184. package/modules/zip/unzipFromArrayBuffer.js +21 -1
  185. package/modules/zip/unzipFromArrayBuffer.js.map +1 -1
  186. package/modules/zip/unzipFromArrayBuffer.test.js.map +1 -0
  187. package/modules/zip/unzipFromStream.fflate.js +41 -35
  188. package/modules/zip/unzipFromStream.fflate.js.map +1 -1
  189. package/modules/zip/unzipFromStream.js +124 -1
  190. package/modules/zip/unzipFromStream.js.map +1 -1
  191. package/modules/zip/unzipFromStream.unzipper.js +19 -35
  192. package/modules/zip/unzipFromStream.unzipper.js.map +1 -1
  193. package/node/index.cjs +3 -0
  194. package/node/index.d.ts +16 -0
  195. package/node/index.js +3 -0
  196. package/package.json +5 -3
  197. package/types/InvalidInputError.d.ts +16 -0
  198. package/types/InvalidSpreadsheetError.d.ts +4 -0
  199. package/types/SheetNotFoundError.d.ts +4 -0
  200. package/universal/index.cjs +3 -0
  201. package/universal/index.d.ts +16 -0
  202. package/universal/index.js +3 -0
  203. package/web-worker/index.cjs +3 -0
  204. package/web-worker/index.d.ts +16 -0
  205. package/web-worker/index.js +3 -0
  206. package/commonjs/export/convertValuesFromUint8ArraysToStrings.js +0 -23
  207. package/commonjs/export/convertValuesFromUint8ArraysToStrings.js.map +0 -1
  208. package/commonjs/xlsx/convertCellsToData2dArray.js +0 -84
  209. package/commonjs/xlsx/convertCellsToData2dArray.js.map +0 -1
  210. package/commonjs/xlsx/dropEmptyColumns.js +0 -40
  211. package/commonjs/xlsx/dropEmptyColumns.js.map +0 -1
  212. package/commonjs/xlsx/dropEmptyColumns.test.js.map +0 -1
  213. package/commonjs/xlsx/dropEmptyRows.js +0 -43
  214. package/commonjs/xlsx/dropEmptyRows.js.map +0 -1
  215. package/commonjs/xlsx/dropEmptyRows.test.js.map +0 -1
  216. package/commonjs/xlsx/parseCellValue.js +0 -189
  217. package/commonjs/xlsx/parseCellValue.js.map +0 -1
  218. package/commonjs/xlsx/parseNumber.js +0 -20
  219. package/commonjs/xlsx/parseNumber.js.map +0 -1
  220. package/commonjs/xlsx/parseSharedString.js +0 -64
  221. package/commonjs/xlsx/parseSharedString.js.map +0 -1
  222. package/commonjs/xlsx/parseSheetData.js +0 -68
  223. package/commonjs/xlsx/parseSheetData.js.map +0 -1
  224. package/commonjs/xlsx/reconstructSheetDimensionsFromSheetCells.js +0 -29
  225. package/commonjs/xlsx/reconstructSheetDimensionsFromSheetCells.js.map +0 -1
  226. package/commonjs/xml/parseXmlStream.saxen.code.js +0 -953
  227. package/commonjs/xml/parseXmlStream.saxen.code.js.map +0 -1
  228. package/commonjs/xml/parseXmlStream.test.js.map +0 -1
  229. package/modules/export/convertValuesFromUint8ArraysToStrings.js +0 -17
  230. package/modules/export/convertValuesFromUint8ArraysToStrings.js.map +0 -1
  231. package/modules/xlsx/convertCellsToData2dArray.js +0 -77
  232. package/modules/xlsx/convertCellsToData2dArray.js.map +0 -1
  233. package/modules/xlsx/dropEmptyColumns.js +0 -34
  234. package/modules/xlsx/dropEmptyColumns.js.map +0 -1
  235. package/modules/xlsx/dropEmptyColumns.test.js.map +0 -1
  236. package/modules/xlsx/dropEmptyRows.js +0 -37
  237. package/modules/xlsx/dropEmptyRows.js.map +0 -1
  238. package/modules/xlsx/dropEmptyRows.test.js.map +0 -1
  239. package/modules/xlsx/parseCellValue.js +0 -183
  240. package/modules/xlsx/parseCellValue.js.map +0 -1
  241. package/modules/xlsx/parseNumber.js +0 -14
  242. package/modules/xlsx/parseNumber.js.map +0 -1
  243. package/modules/xlsx/parseSharedString.js +0 -55
  244. package/modules/xlsx/parseSharedString.js.map +0 -1
  245. package/modules/xlsx/parseSheetData.js +0 -55
  246. package/modules/xlsx/parseSheetData.js.map +0 -1
  247. package/modules/xlsx/reconstructSheetDimensionsFromSheetCells.js +0 -23
  248. package/modules/xlsx/reconstructSheetDimensionsFromSheetCells.js.map +0 -1
  249. package/modules/xml/parseXmlStream.saxen.code.js +0 -947
  250. package/modules/xml/parseXmlStream.saxen.code.js.map +0 -1
  251. package/modules/xml/parseXmlStream.test.js.map +0 -1
@@ -74,6 +74,7 @@ import { Unzip } from 'fflate';
74
74
 
75
75
  import { Buffer } from 'node:buffer';
76
76
  import zlib from 'node:zlib';
77
+ import UnzipError, { createUnzipError } from './UnzipError.js';
77
78
 
78
79
  // Native `zlib` is faster than `UnzipInflate`.
79
80
  // * When decompressing a `1 MB` `.xlsx` file, the decompression time is `100 ms`
@@ -92,45 +93,29 @@ var USE_ZLIB_DECOMPRESSOR = true;
92
93
  // * When decompressing a `50 MB` `.xlsx` file, the decompression time is `3600 ms`
93
94
  // when using "sync" decompressor and `3200 ms` when using "async" decompressor.
94
95
  var USE_ASYNC_FFLATE_DECOMPRESSOR = false;
96
+ var PROMISE_RESOLVE_VALUE = undefined;
95
97
 
96
98
  /**
97
99
  * Reads `*.zip` file contents.
98
100
  * @param {Stream} stream
99
- * @return {Promise<Record<string,Buffer>>} Resolves to an object holding `*.zip` file entries. P.S. `Buffer` is a `Uint8Array`.
101
+ * @param {function} onFile
102
+ * @param {function} onFileData
103
+ * @param {function} onFileDataEnd
104
+ * @return {Promise<void>}
100
105
  */
101
- export default function unzipFromStream(stream) {
102
- var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
103
- filter = _ref.filter;
104
- // The `files` object stores the files and their contents.
105
- var files = {};
106
- var filesChunks = {};
107
- var onFile = function onFile(filePath) {
108
- // See if this file should be ignored.
109
- // If it should, this entry won't be processed, i.e. `Unzip` will not try
110
- // to decompress its data, and will just discard it.
111
- if (filter && !filter({
112
- path: filePath
113
- })) {
114
- return false;
115
- }
116
- filesChunks[filePath] = [];
117
- };
118
- var onFileData = function onFileData(filePath, chunk) {
119
- filesChunks[filePath].push(chunk);
120
- };
121
- var onFileDataEnd = function onFileDataEnd(filePath) {
122
- files[filePath] = Buffer.concat(filesChunks[filePath]);
123
- delete filesChunks[filePath];
124
- };
125
- return unzipFromStream_(stream, onFile, onFileData, onFileDataEnd).then(function () {
126
- return files;
127
- });
128
- }
129
- var PROMISE_RESOLVE_VALUE = undefined;
130
- function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {
106
+ export default function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {
131
107
  return new Promise(function (resolve, reject) {
132
108
  var errored = false;
133
109
  var onError = function onError(error) {
110
+ // If `fflate` throws its specific error then it implies that the `.zip` file is not valid.
111
+ //
112
+ // By default, `fflate` uses it's own pure-js unzipper.
113
+ // It could also be configured to use `node:zlib` native module instead.
114
+ // In that case, it could throw a `code: "Z_DATA_ERROR"` too.
115
+ //
116
+ if (isFlateError(error) || error.code === 'Z_DATA_ERROR') {
117
+ error = createUnzipError(error);
118
+ }
134
119
  if (!errored) {
135
120
  errored = true;
136
121
  reject(error);
@@ -151,7 +136,7 @@ function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {
151
136
  };
152
137
  var _createZipFileValidat = createZipFileValidator(function (isValid) {
153
138
  if (!isValid) {
154
- onError(new Error('Invalid `.zip` archive'));
139
+ onError(new UnzipError('INVALID_ZIP'));
155
140
  }
156
141
  }),
157
142
  validateChunk = _createZipFileValidat.validateChunk;
@@ -170,7 +155,10 @@ function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {
170
155
  if (entry.name.endsWith('/')) {
171
156
  return;
172
157
  }
173
- if (onFile(entry.name) === false) {
158
+
159
+ // `entry.originalSize` property will not be present for `.zip` archives
160
+ // that were created in a streaming fashion.
161
+ if (onFile(entry.name, entry.originalSize) === false) {
174
162
  return;
175
163
  }
176
164
  stillDecompressingEntriesCount++;
@@ -181,7 +169,17 @@ function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {
181
169
  if (error) {
182
170
  return onError(error);
183
171
  }
184
- onFileData(entry.name, chunk);
172
+ // Sometimes, `chunk` is a `Buffer`. Other times, it's a `Uint8Array`.
173
+ // It's not really clear in which exact circumstances it's one or the other.
174
+ if (chunk instanceof Buffer) {
175
+ onFileData(entry.name, chunk);
176
+ } else if (chunk instanceof Uint8Array) {
177
+ onFileData(entry.name, Buffer.from(chunk));
178
+ } else {
179
+ // This error is not technically possible.
180
+ // In case it is thrown, it means that there's a bug in the code.
181
+ throw new Error('Unsupported type of chunk', chunk);
182
+ }
185
183
  if (isLast) {
186
184
  stillDecompressingEntriesCount--;
187
185
  onFileDataEnd(entry.name);
@@ -331,6 +329,14 @@ var NativeZlibInflate = /*#__PURE__*/function () {
331
329
  }
332
330
  }]);
333
331
  return NativeZlibInflate;
334
- }();
332
+ }(); // This function attempts to guess if a given `error` was thrown by `fflate`.
335
333
  _defineProperty(NativeZlibInflate, "compression", 8);
334
+ function isFlateError(error) {
335
+ // `fflate` doesn't export a `FlateError` class.
336
+ // https://github.com/101arrowz/fflate/issues/290
337
+ // return error instanceof FlateError
338
+
339
+ // Here, it attempts to guess if an `error` is a `FlateError` by checking if `error.code` is a `number`.
340
+ return typeof error.code === 'number';
341
+ }
336
342
  //# sourceMappingURL=unzipFromStream.fflate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"unzipFromStream.fflate.js","names":["Unzip","Buffer","zlib","USE_ZLIB_DECOMPRESSOR","USE_ASYNC_FFLATE_DECOMPRESSOR","unzipFromStream","stream","_ref","arguments","length","undefined","filter","files","filesChunks","onFile","filePath","path","onFileData","chunk","push","onFileDataEnd","concat","unzipFromStream_","then","PROMISE_RESOLVE_VALUE","Promise","resolve","reject","errored","onError","error","stillDecompressingEntriesCount","noMoreEntries","resolveIfDone","_createZipFileValidat","createZipFileValidator","isValid","Error","validateChunk","unzip","entry","name","endsWith","ondata","isLast","start","register","NativeZlibInflate","AsyncUnzipInflate","UnzipInflate","on","Uint8Array","onValidationResult","firstBytesCount","firstBytes","firstBytesCheckResult","i","_this","_classCallCheck","inflate","createInflateRaw","_createClass","key","value","write","from","end","terminate","destroy","_defineProperty"],"sources":["../../source/zip/unzipFromStream.fflate.js"],"sourcesContent":["// Considerations:\r\n//\r\n// * `fflate` uses a pure-javascript implementation of `.zip` compression/decompression by default.\r\n// This pure-javascript implementation is about 2x slower than Node.js's \"native\" `zlib` module.\r\n// This issue is worked around by \"marrying\" `fflate` with Node.js's `zlib` \"native\" module through some tinkering.\r\n// https://github.com/101arrowz/fflate/issues/284\r\n//\r\n// * Even though `fflate` implements a \"streaming\" mode of its own,\r\n// it does not implement Node.js streams \"contract\", i.e. it just unzips the archive\r\n// as fast as it can consume it from the input stream, without throttling the data throughput\r\n// in cases when the input data flows faster than `fflate` can process it. This means that\r\n// in the \"worst case\" scenario when `fflate`'s decompressor is unable to keep up with the\r\n// data influx, it would \"buffer\" the entire `.zip` archive in RAM until it's processed.\r\n// And while this is no big deal by any means, it's still not as elegant as adhering to\r\n// Node.js streaming protocol.\r\n\r\n// This code was originally submitted by Stian Jensen.\r\n// https://github.com/catamphetamine/read-excel-file/pull/122\r\n// https://github.com/catamphetamine/read-excel-file/pull/123\r\n\r\n// A `*.zip` file consists of individual file entries with the \"total\" summary section\r\n// placed at the end of the file rather than at the start of it, which was originally done\r\n// to allow for easy append of data to a given `.zip` file.\r\n// https://en.wikipedia.org/wiki/ZIP_(file_format)\r\n//\r\n// But this also means that reading a `*.zip` file from a stream can't really be done\r\n// using the \"officially recommended\" way of first reading the \"total\" summary section\r\n// and only then reading the individual file entries specified in that summary section.\r\n//\r\n// So in order to be able to read a `*.zip` file from a stream, some corners have to be cut.\r\n// For example, the \"total\" summary section is completely ignored and instead the reader\r\n// should adopt \"data recovery\" software approach — it should proactively \"scan\" the input stream\r\n// for individual file entries and handle them one-by-one as they come.\r\n//\r\n// Such approach doesn't seem to contradict with the XLSX specification\r\n// because an `*.xlsx` files is supposed to be a normal `.zip` archive\r\n// without any \"trickery\" such as \"deleted\" files or \"garbage\" data\r\n// hiding under the hood.\r\n//\r\n// So when handling `*.xlsx` file, we assume that each such file must start\r\n// with an individual file entry followed by another individual file entry, etc.\r\n//\r\n// When the \"summary\" section is reached, we assume that the archive has ended.\r\n//\r\n// To read a `.zip` archive, the code uses `fflate`'s `Unzip` class.\r\n// The actual decompression could be performed either by using\r\n// `fflate`'s pure-javascript `UnzipInflate` implementation, which is\r\n// about 2x slower than Node.js's \"native\" module `zlib`,\r\n// or it could use the aforementioned `zlib` module as a drop-in replacement\r\n// (with some tinkering).\r\n//\r\n// The `Unzip` class doesn't speak the Node.js stream interface, and `fflate`'s readme\r\n// doesn't include a clear \"reading a `.zip` file from a Node.js stream\" section.\r\n// https://github.com/101arrowz/fflate/issues/251\r\n// Instead, the `Unzip` class has its own `push(chunk)` / `onfile` / `entry.ondata` protocol.\r\n// This code reads the binary input stream and forwards each chunk of it to `unzip.push()`,\r\n// and then collects the decompressed file entries.\r\n//\r\n// P.S. In the comments to `UnzipInflate` in `fflate` package, it says:\r\n// \"Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for better performance.\"\r\n// But there seems to be no `AsyncZipInflate` class in the `fflate` package.\r\n// https://github.com/101arrowz/fflate/issues/277\r\n// So just the regular `UnzipInflate` is used here.\r\n//\r\nimport { Unzip } from 'fflate'\r\n// import { AsyncUnzipInflate, UnzipInflate } from 'fflate'\r\n\r\nimport { Buffer } from 'node:buffer'\r\nimport zlib from 'node:zlib'\r\n\r\n// Native `zlib` is faster than `UnzipInflate`.\r\n// * When decompressing a `1 MB` `.xlsx` file, the decompression time is `100 ms`\r\n// when using `zlib` decompressor and `150 ms` when using `fflate` \"sync\" decompressor.\r\n// * When decompressing a `10 MB` `.xlsx` file, the decompression time is `500 ms`\r\n// when using `zlib` decompressor and `650 ms` when using `fflate` \"sync\" decompressor.\r\n// * When decompressing a `50 MB` `.xlsx` file, the decompression time is `2800 ms`\r\n// when using `zlib` decompressor and `3600 ms` when using `fflate` \"sync\" decompressor.\r\nconst USE_ZLIB_DECOMPRESSOR = true\r\n\r\n// `AsyncZipInflate` is faster than `UnzipInflate` for `.xlsx` files that're larger\r\n// than a few megabytes.\r\n// * When decompressing a `1 MB` `.xlsx` file, the decompression time is `150 ms`\r\n// when using \"sync\" decompressor and `170 ms` when using \"async\" decompressor.\r\n// * When decompressing a `10 MB` `.xlsx` file, the decompression time is about the same.\r\n// * When decompressing a `50 MB` `.xlsx` file, the decompression time is `3600 ms`\r\n// when using \"sync\" decompressor and `3200 ms` when using \"async\" decompressor.\r\nconst USE_ASYNC_FFLATE_DECOMPRESSOR = false\r\n\r\n/**\r\n * Reads `*.zip` file contents.\r\n * @param {Stream} stream\r\n * @return {Promise<Record<string,Buffer>>} Resolves to an object holding `*.zip` file entries. P.S. `Buffer` is a `Uint8Array`.\r\n */\r\nexport default function unzipFromStream(stream, { filter } = {}) {\r\n\t// The `files` object stores the files and their contents.\r\n\tconst files = {}\r\n\tconst filesChunks = {}\r\n\r\n\tconst onFile = (filePath) => {\r\n\t\t// See if this file should be ignored.\r\n\t\t// If it should, this entry won't be processed, i.e. `Unzip` will not try\r\n\t\t// to decompress its data, and will just discard it.\r\n\t\tif (filter && !filter({ path: filePath })) {\r\n\t\t\treturn false\r\n\t\t}\r\n\t\tfilesChunks[filePath] = []\r\n\t}\r\n\r\n\tconst onFileData = (filePath, chunk) => {\r\n\t\tfilesChunks[filePath].push(chunk)\r\n\t}\r\n\r\n\tconst onFileDataEnd = (filePath) => {\r\n\t\tfiles[filePath] = Buffer.concat(filesChunks[filePath])\r\n\t\tdelete filesChunks[filePath]\r\n\t}\r\n\r\n\treturn unzipFromStream_(stream, onFile, onFileData, onFileDataEnd).then(() => {\r\n\t\treturn files\r\n\t})\r\n}\r\n\r\nconst PROMISE_RESOLVE_VALUE = undefined\r\n\r\nfunction unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {\r\n\treturn new Promise((resolve, reject) => {\r\n\t\tlet errored = false\r\n\r\n\t\tconst onError = (error) => {\r\n\t\t\tif (!errored) {\r\n\t\t\t\terrored = true\r\n\t\t\t\treject(error)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// The native `zlib` decoder finishes inflating an entry asynchronously, so\r\n\t\t// the archive's `end` event can fire while entries are still decompressing.\r\n\t\t// It counts \"decompression still in-progress\" entries the decompression for which\r\n\t\t// has started but not yet finished, and the main promise only resolves once\r\n\t\t// the input stream has ended *and* every entry is done decompressing.\r\n\t\tlet stillDecompressingEntriesCount = 0\r\n\t\tlet noMoreEntries = false\r\n\t\tconst resolveIfDone = () => {\r\n\t\t\tif (!errored && noMoreEntries && stillDecompressingEntriesCount === 0) {\r\n\t\t\t\tresolve(PROMISE_RESOLVE_VALUE)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst { validateChunk } = createZipFileValidator((isValid) => {\r\n\t\t\tif (!isValid) {\r\n\t\t\t\tonError(new Error('Invalid `.zip` archive'))\r\n\t\t\t}\r\n\t\t})\r\n\r\n\t\t// `Unzip` discovers each individual file entry in the input data stream\r\n\t\t// and then calls the callback function for each such entry.\r\n\t\tconst unzip = new Unzip((entry) => {\r\n\t\t\t// If there already was an error while reading this `.zip` file,\r\n\t\t\t// ignore any follow-up entries.\r\n\t\t\tif (errored) {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\t// Skip directory entries (their names end with a slash).\r\n\t\t\t// Only files are of any interest.\r\n\t\t\tif (entry.name.endsWith('/')) {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tif (onFile(entry.name) === false) {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tstillDecompressingEntriesCount++\r\n\r\n\t\t\t// `entry.ondata` is called with each decompressed chunk of the entry,\r\n\t\t\t// and then a final time with `isLast === true` once the entry is complete.\r\n\t\t\tentry.ondata = (error, chunk, isLast) => {\r\n\t\t\t\tif (error) {\r\n\t\t\t\t\treturn onError(error)\r\n\t\t\t\t}\r\n\t\t\t\tonFileData(entry.name, chunk)\r\n\t\t\t\tif (isLast) {\r\n\t\t\t\t\tstillDecompressingEntriesCount--\r\n\t\t\t\t\tonFileDataEnd(entry.name)\r\n\t\t\t\t\tresolveIfDone()\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Start decompressing this entry.\r\n\t\t\tentry.start()\r\n\t\t})\r\n\r\n\t\t// Register the decompressor for the data that was compressed using\r\n\t\t// `DEFLATE` compression algorithm (compression method `8`),\r\n\t\t// which is what `.xlsx` files use.\r\n\t\tunzip.register(USE_ZLIB_DECOMPRESSOR ? NativeZlibInflate : (USE_ASYNC_FFLATE_DECOMPRESSOR ? AsyncUnzipInflate : UnzipInflate))\r\n\r\n\t\tstream\r\n\t\t\t// Catch errors emitted from the input stream (for example, a file read error).\r\n\t\t\t.on('error', onError)\r\n\t\t\t// When another chunk of data is read from the input stream.\r\n\t\t\t.on('data', (chunk) => {\r\n\t\t\t\t// If there already was an error while reading this `.zip` file,\r\n\t\t\t\t// ignore any follow-up data chunks.\r\n\t\t\t\tif (errored) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\t// Validate the `.zip` archive as its data comes through.\r\n\t\t\t\tvalidateChunk(chunk)\r\n\t\t\t\t// If the `.zip` archive is found to be invalid, stop any further\r\n\t\t\t\t// processing of it.\r\n\t\t\t\tif (errored) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\t// Push the next data chunk to `fflate`'s `Unzip` class instance.\r\n\t\t\t\t//\r\n\t\t\t\t// The `.push()` function of `fflate`'s own `ZipInflate` decompressor is synchronous,\r\n\t\t\t\t// meaning that by the time it returns, any complete files entries encountered so far\r\n\t\t\t\t// have already been decompressed and populated in the `files` object.\r\n\t\t\t\t//\r\n\t\t\t\t// The `.push()` function of `NativeZlibInflate` decompressor is asynchronous,\r\n\t\t\t\t// so it requires hacking around with the counter of \"still being decompressed\" entries\r\n\t\t\t\t// in order to detect the actual finish of the archive's decompression process.\r\n\t\t\t\t//\r\n\t\t\t\ttry {\r\n\t\t\t\t\tunzip.push(chunk, false)\r\n\t\t\t\t} catch (error) {\r\n\t\t\t\t\tonError(error)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t// When there's no more data in the input stream to consume,\r\n\t\t\t// finish reading the `.zip` archive.\r\n\t\t\t.on('end', () => {\r\n\t\t\t\t// If there were any errors when reading the `.zip` archive,\r\n\t\t\t\t// don't `resolve()` with anything.\r\n\t\t\t\tif (errored) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\ttry {\r\n\t\t\t\t\t// Signal the end of the archive to `fflate`'s `Unzip` class instance.\r\n\t\t\t\t\t// It will flush any remaining state in it.\r\n\t\t\t\t\tunzip.push(new Uint8Array(0), true)\r\n\t\t\t\t\t// The input stream has ended.\r\n\t\t\t\t\tnoMoreEntries = true\r\n\t\t\t\t\t// The entries may still be decompressing asynchronously.\r\n\t\t\t\t\t// In that case, resolve once they all finish decompresssing.\r\n\t\t\t\t\t// Or, resolve if all entries have already finished decompressing by now.\r\n\t\t\t\t\tresolveIfDone()\r\n\t\t\t\t} catch (error) {\r\n\t\t\t\t\tonError(error)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t})\r\n}\r\n\r\n// Every section in a `.zip` archive is marked with 4 bytes, the first two of which\r\n// are `0x50` and `0x4B`, which reads \"PK\", referencing the initials of the inventor Phil Katz.\r\n//\r\n// It looks like `fflate`'s `Unzip` doesn't ever complain about whatever data is thrown at it.\r\n// Due to how `.zip` file format is defined, \"garbage\" data could be placed at various\r\n// places in it and it'd still be a valid `.zip` archive. It's likely that for this reason\r\n// `fflate` doesn't ever complain and simply emits no entries when fed any kind of invalid data.\r\n//\r\n// In order to introduce some basic validation, here we specifically demand\r\n// that a `.zip` archive must at least start with an individual file entry\r\n// because an `.xlsx` file creator softwared really shouldn't attempt doing\r\n// anything \"funny\" when writing a file, hence this adherence requirement.\r\n//\r\nfunction createZipFileValidator(onValidationResult) {\r\n\tconst firstBytesCount = 2\r\n\tconst firstBytes = []\r\n\tlet firstBytesCheckResult\r\n\treturn {\r\n\t\tvalidateChunk(chunk) {\r\n\t\t\tif (firstBytes.length < 2) {\r\n\t\t\t\tlet i = 0\r\n\t\t\t\twhile (i < chunk.length && i < firstBytesCount) {\r\n\t\t\t\t\tfirstBytes.push(chunk[i])\r\n\t\t\t\t\ti++\r\n\t\t\t\t}\r\n\t\t\t\tif (firstBytes.length === 2) {\r\n\t\t\t\t\tconst isValid = firstBytes[0] === 0x50 && firstBytes[1] === 0x4B\r\n\t\t\t\t\tonValidationResult(isValid)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// An implemenation of a `DEFLATE` decompressor for `fflate`'s `Unzip` class\r\n// that uses Node.js's \"native\" module `zlib`.\r\n//\r\n// It implements `fflate`'s decoder interface: `Unzip` constructs one decoder\r\n// per each entry, sets `ondata(error, chunk, isLast)` callback on it, and feeds it\r\n// the entry's compressed bytes by calling `push(chunk, isLast)` method.\r\n//\r\n// Unlike `fflate`'s synchronous `UnzipInflate` decoder, `zlib` decompresses asynchronously,\r\n// so an entry only finishes some \"ticks\" after its last chunk of its data is `push()`ed\r\n// by `fflate`'s `Unzip` class. To work around this issue, pending entries counter is used\r\n// to track when the archive really finishes unpacking.\r\n//\r\nclass NativeZlibInflate {\r\n\tstatic compression = 8\r\n\r\n\tconstructor() {\r\n\t\tthis.inflate = zlib.createInflateRaw()\r\n\t\tthis.inflate.on('data', (chunk) => this.ondata(null, chunk, false))\r\n\t\tthis.inflate.on('end', () => this.ondata(null, new Uint8Array(0), true))\r\n\t\tthis.inflate.on('error', (error) => this.ondata(error, null, false))\r\n\t}\r\n\r\n\tpush(chunk, isLast) {\r\n\t\tthis.inflate.write(Buffer.from(chunk))\r\n\t\tif (isLast) {\r\n\t\t\tthis.inflate.end()\r\n\t\t}\r\n\t}\r\n\r\n\tterminate() {\r\n\t\tthis.inflate.destroy()\r\n\t}\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"unzipFromStream.fflate.js","names":["Unzip","Buffer","zlib","UnzipError","createUnzipError","USE_ZLIB_DECOMPRESSOR","USE_ASYNC_FFLATE_DECOMPRESSOR","PROMISE_RESOLVE_VALUE","undefined","unzipFromStream_","stream","onFile","onFileData","onFileDataEnd","Promise","resolve","reject","errored","onError","error","isFlateError","code","stillDecompressingEntriesCount","noMoreEntries","resolveIfDone","_createZipFileValidat","createZipFileValidator","isValid","validateChunk","unzip","entry","name","endsWith","originalSize","ondata","chunk","isLast","Uint8Array","from","Error","start","register","NativeZlibInflate","AsyncUnzipInflate","UnzipInflate","on","push","onValidationResult","firstBytesCount","firstBytes","firstBytesCheckResult","length","i","_this","_classCallCheck","inflate","createInflateRaw","_createClass","key","value","write","end","terminate","destroy","_defineProperty"],"sources":["../../source/zip/unzipFromStream.fflate.js"],"sourcesContent":["// Considerations:\r\n//\r\n// * `fflate` uses a pure-javascript implementation of `.zip` compression/decompression by default.\r\n// This pure-javascript implementation is about 2x slower than Node.js's \"native\" `zlib` module.\r\n// This issue is worked around by \"marrying\" `fflate` with Node.js's `zlib` \"native\" module through some tinkering.\r\n// https://github.com/101arrowz/fflate/issues/284\r\n//\r\n// * Even though `fflate` implements a \"streaming\" mode of its own,\r\n// it does not implement Node.js streams \"contract\", i.e. it just unzips the archive\r\n// as fast as it can consume it from the input stream, without throttling the data throughput\r\n// in cases when the input data flows faster than `fflate` can process it. This means that\r\n// in the \"worst case\" scenario when `fflate`'s decompressor is unable to keep up with the\r\n// data influx, it would \"buffer\" the entire `.zip` archive in RAM until it's processed.\r\n// And while this is no big deal by any means, it's still not as elegant as adhering to\r\n// Node.js streaming protocol.\r\n\r\n// This code was originally submitted by Stian Jensen.\r\n// https://github.com/catamphetamine/read-excel-file/pull/122\r\n// https://github.com/catamphetamine/read-excel-file/pull/123\r\n\r\n// A `*.zip` file consists of individual file entries with the \"total\" summary section\r\n// placed at the end of the file rather than at the start of it, which was originally done\r\n// to allow for easy append of data to a given `.zip` file.\r\n// https://en.wikipedia.org/wiki/ZIP_(file_format)\r\n//\r\n// But this also means that reading a `*.zip` file from a stream can't really be done\r\n// using the \"officially recommended\" way of first reading the \"total\" summary section\r\n// and only then reading the individual file entries specified in that summary section.\r\n//\r\n// So in order to be able to read a `*.zip` file from a stream, some corners have to be cut.\r\n// For example, the \"total\" summary section is completely ignored and instead the reader\r\n// should adopt \"data recovery\" software approach — it should proactively \"scan\" the input stream\r\n// for individual file entries and handle them one-by-one as they come.\r\n//\r\n// Such approach doesn't seem to contradict with the XLSX specification\r\n// because an `*.xlsx` files is supposed to be a normal `.zip` archive\r\n// without any \"trickery\" such as \"deleted\" files or \"garbage\" data\r\n// hiding under the hood.\r\n//\r\n// So when handling `*.xlsx` file, we assume that each such file must start\r\n// with an individual file entry followed by another individual file entry, etc.\r\n//\r\n// When the \"summary\" section is reached, we assume that the archive has ended.\r\n//\r\n// To read a `.zip` archive, the code uses `fflate`'s `Unzip` class.\r\n// The actual decompression could be performed either by using\r\n// `fflate`'s pure-javascript `UnzipInflate` implementation, which is\r\n// about 2x slower than Node.js's \"native\" module `zlib`,\r\n// or it could use the aforementioned `zlib` module as a drop-in replacement\r\n// (with some tinkering).\r\n//\r\n// The `Unzip` class doesn't speak the Node.js stream interface, and `fflate`'s readme\r\n// doesn't include a clear \"reading a `.zip` file from a Node.js stream\" section.\r\n// https://github.com/101arrowz/fflate/issues/251\r\n// Instead, the `Unzip` class has its own `push(chunk)` / `onfile` / `entry.ondata` protocol.\r\n// This code reads the binary input stream and forwards each chunk of it to `unzip.push()`,\r\n// and then collects the decompressed file entries.\r\n//\r\n// P.S. In the comments to `UnzipInflate` in `fflate` package, it says:\r\n// \"Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for better performance.\"\r\n// But there seems to be no `AsyncZipInflate` class in the `fflate` package.\r\n// https://github.com/101arrowz/fflate/issues/277\r\n// So just the regular `UnzipInflate` is used here.\r\n//\r\nimport { Unzip } from 'fflate'\r\n// import { AsyncUnzipInflate, UnzipInflate } from 'fflate'\r\n\r\nimport { Buffer } from 'node:buffer'\r\nimport zlib from 'node:zlib'\r\n\r\nimport UnzipError, { createUnzipError } from './UnzipError.js'\r\n\r\n// Native `zlib` is faster than `UnzipInflate`.\r\n// * When decompressing a `1 MB` `.xlsx` file, the decompression time is `100 ms`\r\n// when using `zlib` decompressor and `150 ms` when using `fflate` \"sync\" decompressor.\r\n// * When decompressing a `10 MB` `.xlsx` file, the decompression time is `500 ms`\r\n// when using `zlib` decompressor and `650 ms` when using `fflate` \"sync\" decompressor.\r\n// * When decompressing a `50 MB` `.xlsx` file, the decompression time is `2800 ms`\r\n// when using `zlib` decompressor and `3600 ms` when using `fflate` \"sync\" decompressor.\r\nconst USE_ZLIB_DECOMPRESSOR = true\r\n\r\n// `AsyncZipInflate` is faster than `UnzipInflate` for `.xlsx` files that're larger\r\n// than a few megabytes.\r\n// * When decompressing a `1 MB` `.xlsx` file, the decompression time is `150 ms`\r\n// when using \"sync\" decompressor and `170 ms` when using \"async\" decompressor.\r\n// * When decompressing a `10 MB` `.xlsx` file, the decompression time is about the same.\r\n// * When decompressing a `50 MB` `.xlsx` file, the decompression time is `3600 ms`\r\n// when using \"sync\" decompressor and `3200 ms` when using \"async\" decompressor.\r\nconst USE_ASYNC_FFLATE_DECOMPRESSOR = false\r\n\r\nconst PROMISE_RESOLVE_VALUE = undefined\r\n\r\n/**\r\n * Reads `*.zip` file contents.\r\n * @param {Stream} stream\r\n * @param {function} onFile\r\n * @param {function} onFileData\r\n * @param {function} onFileDataEnd\r\n * @return {Promise<void>}\r\n */\r\nexport default function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {\r\n\treturn new Promise((resolve, reject) => {\r\n\t\tlet errored = false\r\n\r\n\t\tconst onError = (error) => {\r\n\t\t\t// If `fflate` throws its specific error then it implies that the `.zip` file is not valid.\r\n\t\t\t//\r\n\t\t\t// By default, `fflate` uses it's own pure-js unzipper.\r\n\t\t\t// It could also be configured to use `node:zlib` native module instead.\r\n\t\t\t// In that case, it could throw a `code: \"Z_DATA_ERROR\"` too.\r\n\t\t\t//\r\n\t\t\tif (isFlateError(error) || error.code === 'Z_DATA_ERROR') {\r\n\t\t\t\terror = createUnzipError(error)\r\n\t\t\t}\r\n\t\t\tif (!errored) {\r\n\t\t\t\terrored = true\r\n\t\t\t\treject(error)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// The native `zlib` decoder finishes inflating an entry asynchronously, so\r\n\t\t// the archive's `end` event can fire while entries are still decompressing.\r\n\t\t// It counts \"decompression still in-progress\" entries the decompression for which\r\n\t\t// has started but not yet finished, and the main promise only resolves once\r\n\t\t// the input stream has ended *and* every entry is done decompressing.\r\n\t\tlet stillDecompressingEntriesCount = 0\r\n\t\tlet noMoreEntries = false\r\n\t\tconst resolveIfDone = () => {\r\n\t\t\tif (!errored && noMoreEntries && stillDecompressingEntriesCount === 0) {\r\n\t\t\t\tresolve(PROMISE_RESOLVE_VALUE)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst { validateChunk } = createZipFileValidator((isValid) => {\r\n\t\t\tif (!isValid) {\r\n\t\t\t\tonError(new UnzipError('INVALID_ZIP'))\r\n\t\t\t}\r\n\t\t})\r\n\r\n\t\t// `Unzip` discovers each individual file entry in the input data stream\r\n\t\t// and then calls the callback function for each such entry.\r\n\t\tconst unzip = new Unzip((entry) => {\r\n\t\t\t// If there already was an error while reading this `.zip` file,\r\n\t\t\t// ignore any follow-up entries.\r\n\t\t\tif (errored) {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\t// Skip directory entries (their names end with a slash).\r\n\t\t\t// Only files are of any interest.\r\n\t\t\tif (entry.name.endsWith('/')) {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\t// `entry.originalSize` property will not be present for `.zip` archives\r\n\t\t\t// that were created in a streaming fashion.\r\n\t\t\tif (onFile(entry.name, entry.originalSize) === false) {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tstillDecompressingEntriesCount++\r\n\r\n\t\t\t// `entry.ondata` is called with each decompressed chunk of the entry,\r\n\t\t\t// and then a final time with `isLast === true` once the entry is complete.\r\n\t\t\tentry.ondata = (error, chunk, isLast) => {\r\n\t\t\t\tif (error) {\r\n\t\t\t\t\treturn onError(error)\r\n\t\t\t\t}\r\n\t\t\t\t// Sometimes, `chunk` is a `Buffer`. Other times, it's a `Uint8Array`.\r\n\t\t\t\t// It's not really clear in which exact circumstances it's one or the other.\r\n\t\t\t\tif (chunk instanceof Buffer) {\r\n\t\t\t\t\tonFileData(entry.name, chunk)\r\n\t\t\t\t} else if (chunk instanceof Uint8Array) {\r\n\t\t\t\t\tonFileData(entry.name, Buffer.from(chunk))\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// This error is not technically possible.\r\n\t\t\t\t\t// In case it is thrown, it means that there's a bug in the code.\r\n\t\t\t\t\tthrow new Error('Unsupported type of chunk', chunk)\r\n\t\t\t\t}\r\n\t\t\t\tif (isLast) {\r\n\t\t\t\t\tstillDecompressingEntriesCount--\r\n\t\t\t\t\tonFileDataEnd(entry.name)\r\n\t\t\t\t\tresolveIfDone()\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Start decompressing this entry.\r\n\t\t\tentry.start()\r\n\t\t})\r\n\r\n\t\t// Register the decompressor for the data that was compressed using\r\n\t\t// `DEFLATE` compression algorithm (compression method `8`),\r\n\t\t// which is what `.xlsx` files use.\r\n\t\tunzip.register(USE_ZLIB_DECOMPRESSOR ? NativeZlibInflate : (USE_ASYNC_FFLATE_DECOMPRESSOR ? AsyncUnzipInflate : UnzipInflate))\r\n\r\n\t\tstream\r\n\t\t\t// Catch errors emitted from the input stream (for example, a file read error).\r\n\t\t\t.on('error', onError)\r\n\t\t\t// When another chunk of data is read from the input stream.\r\n\t\t\t.on('data', (chunk) => {\r\n\t\t\t\t// If there already was an error while reading this `.zip` file,\r\n\t\t\t\t// ignore any follow-up data chunks.\r\n\t\t\t\tif (errored) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\t// Validate the `.zip` archive as its data comes through.\r\n\t\t\t\tvalidateChunk(chunk)\r\n\t\t\t\t// If the `.zip` archive is found to be invalid, stop any further\r\n\t\t\t\t// processing of it.\r\n\t\t\t\tif (errored) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\t// Push the next data chunk to `fflate`'s `Unzip` class instance.\r\n\t\t\t\t//\r\n\t\t\t\t// The `.push()` function of `fflate`'s own `ZipInflate` decompressor is synchronous,\r\n\t\t\t\t// meaning that by the time it returns, any complete files entries encountered so far\r\n\t\t\t\t// have already been decompressed and populated in the `files` object.\r\n\t\t\t\t//\r\n\t\t\t\t// The `.push()` function of `NativeZlibInflate` decompressor is asynchronous,\r\n\t\t\t\t// so it requires hacking around with the counter of \"still being decompressed\" entries\r\n\t\t\t\t// in order to detect the actual finish of the archive's decompression process.\r\n\t\t\t\t//\r\n\t\t\t\ttry {\r\n\t\t\t\t\tunzip.push(chunk, false)\r\n\t\t\t\t} catch (error) {\r\n\t\t\t\t\tonError(error)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t// When there's no more data in the input stream to consume,\r\n\t\t\t// finish reading the `.zip` archive.\r\n\t\t\t.on('end', () => {\r\n\t\t\t\t// If there were any errors when reading the `.zip` archive,\r\n\t\t\t\t// don't `resolve()` with anything.\r\n\t\t\t\tif (errored) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\ttry {\r\n\t\t\t\t\t// Signal the end of the archive to `fflate`'s `Unzip` class instance.\r\n\t\t\t\t\t// It will flush any remaining state in it.\r\n\t\t\t\t\tunzip.push(new Uint8Array(0), true)\r\n\t\t\t\t\t// The input stream has ended.\r\n\t\t\t\t\tnoMoreEntries = true\r\n\t\t\t\t\t// The entries may still be decompressing asynchronously.\r\n\t\t\t\t\t// In that case, resolve once they all finish decompresssing.\r\n\t\t\t\t\t// Or, resolve if all entries have already finished decompressing by now.\r\n\t\t\t\t\tresolveIfDone()\r\n\t\t\t\t} catch (error) {\r\n\t\t\t\t\tonError(error)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t})\r\n}\r\n\r\n// Every section in a `.zip` archive is marked with 4 bytes, the first two of which\r\n// are `0x50` and `0x4B`, which reads \"PK\", referencing the initials of the inventor Phil Katz.\r\n//\r\n// It looks like `fflate`'s `Unzip` doesn't ever complain about whatever data is thrown at it.\r\n// Due to how `.zip` file format is defined, \"garbage\" data could be placed at various\r\n// places in it and it'd still be a valid `.zip` archive. It's likely that for this reason\r\n// `fflate` doesn't ever complain and simply emits no entries when fed any kind of invalid data.\r\n//\r\n// In order to introduce some basic validation, here we specifically demand\r\n// that a `.zip` archive must at least start with an individual file entry\r\n// because an `.xlsx` file creator softwared really shouldn't attempt doing\r\n// anything \"funny\" when writing a file, hence this adherence requirement.\r\n//\r\nfunction createZipFileValidator(onValidationResult) {\r\n\tconst firstBytesCount = 2\r\n\tconst firstBytes = []\r\n\tlet firstBytesCheckResult\r\n\treturn {\r\n\t\tvalidateChunk(chunk) {\r\n\t\t\tif (firstBytes.length < 2) {\r\n\t\t\t\tlet i = 0\r\n\t\t\t\twhile (i < chunk.length && i < firstBytesCount) {\r\n\t\t\t\t\tfirstBytes.push(chunk[i])\r\n\t\t\t\t\ti++\r\n\t\t\t\t}\r\n\t\t\t\tif (firstBytes.length === 2) {\r\n\t\t\t\t\tconst isValid = firstBytes[0] === 0x50 && firstBytes[1] === 0x4B\r\n\t\t\t\t\tonValidationResult(isValid)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// An implemenation of a `DEFLATE` decompressor for `fflate`'s `Unzip` class\r\n// that uses Node.js's \"native\" module `zlib`.\r\n//\r\n// It implements `fflate`'s decoder interface: `Unzip` constructs one decoder\r\n// per each entry, sets `ondata(error, chunk, isLast)` callback on it, and feeds it\r\n// the entry's compressed bytes by calling `push(chunk, isLast)` method.\r\n//\r\n// Unlike `fflate`'s synchronous `UnzipInflate` decoder, `zlib` decompresses asynchronously,\r\n// so an entry only finishes some \"ticks\" after its last chunk of its data is `push()`ed\r\n// by `fflate`'s `Unzip` class. To work around this issue, pending entries counter is used\r\n// to track when the archive really finishes unpacking.\r\n//\r\nclass NativeZlibInflate {\r\n\tstatic compression = 8\r\n\r\n\tconstructor() {\r\n\t\tthis.inflate = zlib.createInflateRaw()\r\n\t\tthis.inflate.on('data', (chunk) => this.ondata(null, chunk, false))\r\n\t\tthis.inflate.on('end', () => this.ondata(null, new Uint8Array(0), true))\r\n\t\tthis.inflate.on('error', (error) => this.ondata(error, null, false))\r\n\t}\r\n\r\n\tpush(chunk, isLast) {\r\n\t\tthis.inflate.write(Buffer.from(chunk))\r\n\t\tif (isLast) {\r\n\t\t\tthis.inflate.end()\r\n\t\t}\r\n\t}\r\n\r\n\tterminate() {\r\n\t\tthis.inflate.destroy()\r\n\t}\r\n}\r\n\r\n// This function attempts to guess if a given `error` was thrown by `fflate`.\r\nfunction isFlateError(error) {\r\n\t// `fflate` doesn't export a `FlateError` class.\r\n\t// https://github.com/101arrowz/fflate/issues/290\r\n\t// return error instanceof FlateError\r\n\r\n\t// Here, it attempts to guess if an `error` is a `FlateError` by checking if `error.code` is a `number`.\r\n\treturn typeof error.code === 'number'\r\n}\r\n"],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,KAAK,QAAQ,QAAQ;AAC9B;;AAEA,SAASC,MAAM,QAAQ,aAAa;AACpC,OAAOC,IAAI,MAAM,WAAW;AAE5B,OAAOC,UAAU,IAAIC,gBAAgB,QAAQ,iBAAiB;;AAE9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAMC,qBAAqB,GAAG,IAAI;;AAElC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAMC,6BAA6B,GAAG,KAAK;AAE3C,IAAMC,qBAAqB,GAAGC,SAAS;;AAEvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,SAASC,gBAAgBA,CAACC,MAAM,EAAEC,MAAM,EAAEC,UAAU,EAAEC,aAAa,EAAE;EACnF,OAAO,IAAIC,OAAO,CAAC,UAACC,OAAO,EAAEC,MAAM,EAAK;IACvC,IAAIC,OAAO,GAAG,KAAK;IAEnB,IAAMC,OAAO,GAAG,SAAVA,OAAOA,CAAIC,KAAK,EAAK;MAC1B;MACA;MACA;MACA;MACA;MACA;MACA,IAAIC,YAAY,CAACD,KAAK,CAAC,IAAIA,KAAK,CAACE,IAAI,KAAK,cAAc,EAAE;QACzDF,KAAK,GAAGf,gBAAgB,CAACe,KAAK,CAAC;MAChC;MACA,IAAI,CAACF,OAAO,EAAE;QACbA,OAAO,GAAG,IAAI;QACdD,MAAM,CAACG,KAAK,CAAC;MACd;IACD,CAAC;;IAED;IACA;IACA;IACA;IACA;IACA,IAAIG,8BAA8B,GAAG,CAAC;IACtC,IAAIC,aAAa,GAAG,KAAK;IACzB,IAAMC,aAAa,GAAG,SAAhBA,a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"}
@@ -1,3 +1,6 @@
1
+ function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
2
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
1
4
  // `unzipFromStream` function reads `.zip` archive data from a Node.js stream.
2
5
  //
3
6
  // Even though it receives a Node.js stream as an input, it doesn't return the results
@@ -64,5 +67,125 @@
64
67
  // when compared to `zipSync`/`unzipSync`/`zip`/`unzip` functions.
65
68
  // https://github.com/101arrowz/fflate/issues/282
66
69
  //
67
- export { default } from './unzipFromStream.unzipper.js';
70
+ import unzipFromStream_ from './unzipFromStream.unzipper.js';
71
+
72
+ /**
73
+ * Reads `*.zip` file contents.
74
+ * @param {Stream} stream
75
+ * @return {Promise<Record<string,Buffer>>} Resolves to an object holding `*.zip` file entries. P.S. `Buffer` is a `Uint8Array`.
76
+ */
77
+ export default function unzipFromStream(stream) {
78
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
79
+ filter = _ref.filter;
80
+ // The `files` object stores the files and their contents.
81
+ var files = {};
82
+ var filesData = {};
83
+
84
+ // `fileSize` is optional and will not be present for `.zip` archives
85
+ // that were created in a streaming fashion.
86
+ var onFile = function onFile(filePath, fileSize) {
87
+ // See if this file should be ignored.
88
+ // If it should, this entry won't be processed, i.e. `Unzip` will not try
89
+ // to decompress its data, and will just discard it.
90
+ if (filter && !filter({
91
+ path: filePath
92
+ })) {
93
+ return false;
94
+ }
95
+ // filesData[filePath] = []
96
+ filesData[filePath] = {
97
+ size: fileSize,
98
+ chunks: [],
99
+ buffer: fileSize === undefined ? undefined : Buffer.alloc(fileSize),
100
+ position: 0
101
+ };
102
+ };
103
+ var onFileData = function onFileData(filePath, chunk) {
104
+ // filesData[filePath].push(chunk)
105
+ if (filesData[filePath].size === undefined) {
106
+ filesData[filePath].chunks.push(chunk);
107
+ } else {
108
+ filesData[filePath].position += chunk.copy(filesData[filePath].buffer, filesData[filePath].position);
109
+ }
110
+ };
111
+ var onFileDataEnd = function onFileDataEnd(filePath) {
112
+ if (filesData[filePath].size === undefined) {
113
+ // // `Buffer.concat()` will produce a pooled buffer if it's <= 4KB.
114
+ // let buffer = Buffer.concat(filesData[filePath].chunks)
115
+ // if (isBufferPooled(buffer)) {
116
+ // // Here it uses `Buffer.allocUnsafeSlow()` instead of the regular `Buffer.alloc()`.
117
+ // // The reason is that `Buffer.allocUnsafeSlow()` doesn't bother resetting the memory
118
+ // // that is being allocated, which is fine because it will be overwritten anyway.
119
+ // // It could also have used `Buffer.allocUnsafe()` if it wasn't returning pooled buffers,
120
+ // // which is what is being avoided here in the first place.
121
+ // const pooledBuffer = buffer
122
+ // buffer = Buffer.allocUnsafeSlow(buffer.byteLength)
123
+ // pooledBuffer.copy(buffer)
124
+ // }
125
+ // files[filePath] = buffer
126
+ files[filePath] = concatChunksToNonPooledBuffer(filesData[filePath].chunks);
127
+ } else {
128
+ files[filePath] = filesData[filePath].buffer;
129
+ }
130
+ delete filesData[filePath];
131
+ };
132
+ return unzipFromStream_(stream, onFile, onFileData, onFileDataEnd).then(function () {
133
+ return files;
134
+ });
135
+ }
136
+
137
+ // // Node.js allocates small buffers (<= 4KB) in a shared 8KB pool.
138
+ // // This interferes with passing such buffers to another worker thread.
139
+ // // In order for a buffer to be transferrable to another worker thread,
140
+ // // it has to be non-pooled.
141
+ // function isBufferPooled(buffer) {
142
+ // // If it doesn't utilize the full underlying `ArrayBuffer`, it's sliced out of a pool.
143
+ // if (buffer.byteOffset > 0) {
144
+ // return true
145
+ // }
146
+ //
147
+ // // If it starts at `0`, but the underlying allocation is larger than the buffer itself, it's pooled/
148
+ // if (buffer.buffer.byteLength > buffer.byteLength) {
149
+ // return true
150
+ // }
151
+ //
152
+ // return false
153
+ // }
154
+
155
+ // Concatenates chunks into a non-"pooled" buffer.
156
+ //
157
+ // It could've simply used `Buffer.concat(chunks)` to concatenate chunks into a buffer.
158
+ // But, while using `Buffer.concat()` is simple, it results in a Node.js optimization
159
+ // when it uses a "shared" memory region of about `8KB` to co-locate this array buffer
160
+ // with other small array buffers if this array buffer is small too (i.e. <= `4KB`).
161
+ //
162
+ // Such co-location results in the inability to "transfer" this buffer
163
+ // to a worker thread.
164
+ //
165
+ // A workaround is to manually allocate a separate memory region of a certain size.
166
+ // This way, the resulting buffer will be non-"pooled".
167
+ //
168
+ function concatChunksToNonPooledBuffer(chunks) {
169
+ // Calculate the total byte length of all the chunks combined.
170
+ var totalLength = chunks.reduce(function (sum, chunk) {
171
+ return sum + chunk.length;
172
+ }, 0);
173
+
174
+ // Allocate a non-pooled buffer.
175
+ // Here it uses `Buffer.allocUnsafeSlow()` instead of the regular `Buffer.alloc()`.
176
+ // The reason is that `Buffer.allocUnsafeSlow()` doesn't bother resetting the memory
177
+ // that is being allocated, which is fine because it will be overwritten anyway.
178
+ // It could also have used `Buffer.allocUnsafe()` if it wasn't returning pooled buffers,
179
+ // which is what is being avoided here in the first place.
180
+ var buffer = Buffer.allocUnsafeSlow(totalLength);
181
+
182
+ // Manually copy each chunk into the buffer.
183
+ var offset = 0;
184
+ for (var _iterator = _createForOfIteratorHelperLoose(chunks), _step; !(_step = _iterator()).done;) {
185
+ var chunk = _step.value;
186
+ chunk.copy(buffer, offset);
187
+ offset += chunk.length;
188
+ }
189
+ return buffer;
190
+ }
68
191
  //# sourceMappingURL=unzipFromStream.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"unzipFromStream.js","names":["default"],"sources":["../../source/zip/unzipFromStream.js"],"sourcesContent":["// `unzipFromStream` function reads `.zip` archive data from a Node.js stream.\r\n//\r\n// Even though it receives a Node.js stream as an input, it doesn't return the results\r\n// as a Node.js output stream (with `objectMode: true` setting). The reason is that\r\n// the result of this function is then passed to the XML parser which currently\r\n// doesn't support consuming a Node.js stream and instead consumes the entire XML document\r\n// as a single string. This means that implementing streaming output in this function\r\n// wouldn't result in any performance improvements, so currently it simply returns\r\n// a \"map\" of all files in the input `.zip` archive, and that seems to be enough.\r\n//\r\n// Currently, there're two decompressor implementations:\r\n// * `fflate` — a pure-javascript implementation that uses `fflate` package.\r\n// * `unzipper` — a \"native\" Node.js module that uses Node's `zlib` which is written in C.\r\n//\r\n// The implementations are compared in a benchmark:\r\n//\r\n// ```\r\n// npm run test:benchmark:unzipFromStream\r\n// ```\r\n//\r\n// The benchmark tells that `unzipper` is 2x faster than `fflate` with the default `ZipInflate` decompressor.\r\n//\r\n// However, when swapping `fflate`'s default `ZipInflate` decompressor with a custom\r\n// \"native\" `zlib` one, `fflate` happens to be about 20% faster than `unzipper`.\r\n//\r\n// Still, when measuring the total time of parsing `.xlsx` files,\r\n// the time difference becomes unmeasurable between `unzipper`\r\n// and `fflate` with \"native\" `zlib` decompressor,\r\n// as could be seen by running `npm run test:benchmark` command\r\n// where `fflate` is slightly faster than `unzipper`.\r\n//\r\n// * When decompressing a `1 MB` `.xlsx` file, the decompression time is `90 ms`\r\n// when using `fflate` with `zlib` decompressor and `100 ms` when using `unzipper` decompressor.\r\n// * When decompressing a `10 MB` `.xlsx` file, the decompression time is `530 ms`\r\n// when using `fflate` with `zlib` decompressor and `550 ms` when using `unzipper` decompressor.\r\n// * When decompressing a `50 MB` `.xlsx` file, the decompression time is `2800 ms`\r\n// when using `fflate` with `zlib` decompressor and `3000 ms` when using `unzipper` decompressor.\r\n//\r\n// So, at the end of the day, there's no substantial difference between the two\r\n// when it comes to parsing `.xlsx` files, and one may choose depending on personal preference.\r\n//\r\n// And my personal preference leans towards `unzipper` because:\r\n//\r\n// * I personally refactored it into `unzipper-esm` in about 5-6 hours.\r\n// https://github.com/ZJONSSON/node-unzipper/pull/356\r\n//\r\n// * It is designed as a Node.js-first module, i.e. it is designed around the concept\r\n// of Node.js streams \"from the ground up\". Meanwhile, `fflate` is a \"universal\" module\r\n// and it does not implement Node.js streams \"contract\", i.e. it just unzips the archive\r\n// as fast as it can consume it from the input stream, without throttling the data throughput\r\n// in cases when the input data flows faster than `fflate` can process it. This means that\r\n// in the \"worst case\" scenario when `fflate`'s decompressor is unable to keep up with the\r\n// data influx, it would \"buffer\" the entire `.zip` archive in RAM until it's processed.\r\n// And while this is no big deal by any means, it's still not as elegant as adhering to\r\n// Node.js streaming protocol.\r\n//\r\n// * `unzipper` uses native `zlib` decompressor which `fflate` doesn't officially include\r\n// and it had to be hacked into it using a couple of tricks. That's also no big deal,\r\n// but still a slightly weird experience.\r\n//\r\n// * Rumors are circulating that `fflate`'s \"streaming\" `Zip`/`Unzip` classes have a few\r\n// implementation issues when compared to non-\"streaming\" `zipSync`/`unzipSync`/`zip`/`unzip` functions.\r\n// Some people even suggested in the comments that `Zip`/`Unzip` classes are not really production-ready\r\n// when compared to `zipSync`/`unzipSync`/`zip`/`unzip` functions.\r\n// https://github.com/101arrowz/fflate/issues/282\r\n//\r\nexport { default as default } from './unzipFromStream.unzipper.js'"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,OAAkB,QAAQ,+BAA+B"}
1
+ {"version":3,"file":"unzipFromStream.js","names":["unzipFromStream_","unzipFromStream","stream","_ref","arguments","length","undefined","filter","files","filesData","onFile","filePath","fileSize","path","size","chunks","buffer","Buffer","alloc","position","onFileData","chunk","push","copy","onFileDataEnd","concatChunksToNonPooledBuffer","then","totalLength","reduce","sum","allocUnsafeSlow","offset","_iterator","_createForOfIteratorHelperLoose","_step","done","value"],"sources":["../../source/zip/unzipFromStream.js"],"sourcesContent":["// `unzipFromStream` function reads `.zip` archive data from a Node.js stream.\r\n//\r\n// Even though it receives a Node.js stream as an input, it doesn't return the results\r\n// as a Node.js output stream (with `objectMode: true` setting). The reason is that\r\n// the result of this function is then passed to the XML parser which currently\r\n// doesn't support consuming a Node.js stream and instead consumes the entire XML document\r\n// as a single string. This means that implementing streaming output in this function\r\n// wouldn't result in any performance improvements, so currently it simply returns\r\n// a \"map\" of all files in the input `.zip` archive, and that seems to be enough.\r\n//\r\n// Currently, there're two decompressor implementations:\r\n// * `fflate` — a pure-javascript implementation that uses `fflate` package.\r\n// * `unzipper` — a \"native\" Node.js module that uses Node's `zlib` which is written in C.\r\n//\r\n// The implementations are compared in a benchmark:\r\n//\r\n// ```\r\n// npm run test:benchmark:unzipFromStream\r\n// ```\r\n//\r\n// The benchmark tells that `unzipper` is 2x faster than `fflate` with the default `ZipInflate` decompressor.\r\n//\r\n// However, when swapping `fflate`'s default `ZipInflate` decompressor with a custom\r\n// \"native\" `zlib` one, `fflate` happens to be about 20% faster than `unzipper`.\r\n//\r\n// Still, when measuring the total time of parsing `.xlsx` files,\r\n// the time difference becomes unmeasurable between `unzipper`\r\n// and `fflate` with \"native\" `zlib` decompressor,\r\n// as could be seen by running `npm run test:benchmark` command\r\n// where `fflate` is slightly faster than `unzipper`.\r\n//\r\n// * When decompressing a `1 MB` `.xlsx` file, the decompression time is `90 ms`\r\n// when using `fflate` with `zlib` decompressor and `100 ms` when using `unzipper` decompressor.\r\n// * When decompressing a `10 MB` `.xlsx` file, the decompression time is `530 ms`\r\n// when using `fflate` with `zlib` decompressor and `550 ms` when using `unzipper` decompressor.\r\n// * When decompressing a `50 MB` `.xlsx` file, the decompression time is `2800 ms`\r\n// when using `fflate` with `zlib` decompressor and `3000 ms` when using `unzipper` decompressor.\r\n//\r\n// So, at the end of the day, there's no substantial difference between the two\r\n// when it comes to parsing `.xlsx` files, and one may choose depending on personal preference.\r\n//\r\n// And my personal preference leans towards `unzipper` because:\r\n//\r\n// * I personally refactored it into `unzipper-esm` in about 5-6 hours.\r\n// https://github.com/ZJONSSON/node-unzipper/pull/356\r\n//\r\n// * It is designed as a Node.js-first module, i.e. it is designed around the concept\r\n// of Node.js streams \"from the ground up\". Meanwhile, `fflate` is a \"universal\" module\r\n// and it does not implement Node.js streams \"contract\", i.e. it just unzips the archive\r\n// as fast as it can consume it from the input stream, without throttling the data throughput\r\n// in cases when the input data flows faster than `fflate` can process it. This means that\r\n// in the \"worst case\" scenario when `fflate`'s decompressor is unable to keep up with the\r\n// data influx, it would \"buffer\" the entire `.zip` archive in RAM until it's processed.\r\n// And while this is no big deal by any means, it's still not as elegant as adhering to\r\n// Node.js streaming protocol.\r\n//\r\n// * `unzipper` uses native `zlib` decompressor which `fflate` doesn't officially include\r\n// and it had to be hacked into it using a couple of tricks. That's also no big deal,\r\n// but still a slightly weird experience.\r\n//\r\n// * Rumors are circulating that `fflate`'s \"streaming\" `Zip`/`Unzip` classes have a few\r\n// implementation issues when compared to non-\"streaming\" `zipSync`/`unzipSync`/`zip`/`unzip` functions.\r\n// Some people even suggested in the comments that `Zip`/`Unzip` classes are not really production-ready\r\n// when compared to `zipSync`/`unzipSync`/`zip`/`unzip` functions.\r\n// https://github.com/101arrowz/fflate/issues/282\r\n//\r\nimport unzipFromStream_ from './unzipFromStream.unzipper.js'\r\n\r\n/**\r\n * Reads `*.zip` file contents.\r\n * @param {Stream} stream\r\n * @return {Promise<Record<string,Buffer>>} Resolves to an object holding `*.zip` file entries. P.S. `Buffer` is a `Uint8Array`.\r\n */\r\nexport default function unzipFromStream(stream, { filter } = {}) {\r\n\t// The `files` object stores the files and their contents.\r\n\tconst files = {}\r\n\tconst filesData = {}\r\n\r\n\t// `fileSize` is optional and will not be present for `.zip` archives\r\n\t// that were created in a streaming fashion.\r\n\tconst onFile = (filePath, fileSize) => {\r\n\t\t// See if this file should be ignored.\r\n\t\t// If it should, this entry won't be processed, i.e. `Unzip` will not try\r\n\t\t// to decompress its data, and will just discard it.\r\n\t\tif (filter && !filter({ path: filePath })) {\r\n\t\t\treturn false\r\n\t\t}\r\n\t\t// filesData[filePath] = []\r\n\t\tfilesData[filePath] = {\r\n\t\t\tsize: fileSize,\r\n\t\t\tchunks: [],\r\n\t\t\tbuffer: fileSize === undefined ? undefined : Buffer.alloc(fileSize),\r\n\t\t\tposition: 0\r\n\t\t}\r\n\t}\r\n\r\n\tconst onFileData = (filePath, chunk) => {\r\n\t\t// filesData[filePath].push(chunk)\r\n\t\tif (filesData[filePath].size === undefined) {\r\n\t\t\tfilesData[filePath].chunks.push(chunk)\r\n\t\t} else {\r\n\t\t\tfilesData[filePath].position += chunk.copy(filesData[filePath].buffer, filesData[filePath].position)\r\n\t\t}\r\n\t}\r\n\r\n\tconst onFileDataEnd = (filePath) => {\r\n\t\tif (filesData[filePath].size === undefined) {\r\n\t\t\t// // `Buffer.concat()` will produce a pooled buffer if it's <= 4KB.\r\n\t\t\t// let buffer = Buffer.concat(filesData[filePath].chunks)\r\n\t\t\t// if (isBufferPooled(buffer)) {\r\n\t\t\t// \t// Here it uses `Buffer.allocUnsafeSlow()` instead of the regular `Buffer.alloc()`.\r\n\t\t\t// \t// The reason is that `Buffer.allocUnsafeSlow()` doesn't bother resetting the memory\r\n\t\t\t// \t// that is being allocated, which is fine because it will be overwritten anyway.\r\n\t\t\t// \t// It could also have used `Buffer.allocUnsafe()` if it wasn't returning pooled buffers,\r\n\t\t\t// \t// which is what is being avoided here in the first place.\r\n\t\t\t// \tconst pooledBuffer = buffer\r\n\t\t\t// \tbuffer = Buffer.allocUnsafeSlow(buffer.byteLength)\r\n\t\t\t// \tpooledBuffer.copy(buffer)\r\n\t\t\t// }\r\n\t\t\t// files[filePath] = buffer\r\n\t\t\tfiles[filePath] = concatChunksToNonPooledBuffer(filesData[filePath].chunks)\r\n\t\t} else {\r\n\t\t\tfiles[filePath] = filesData[filePath].buffer\r\n\t\t}\r\n\t\tdelete filesData[filePath]\r\n\t}\r\n\r\n\treturn unzipFromStream_(stream, onFile, onFileData, onFileDataEnd)\r\n\t\t.then(() => files)\r\n}\r\n\r\n// // Node.js allocates small buffers (<= 4KB) in a shared 8KB pool.\r\n// // This interferes with passing such buffers to another worker thread.\r\n// // In order for a buffer to be transferrable to another worker thread,\r\n// // it has to be non-pooled.\r\n// function isBufferPooled(buffer) {\r\n// \t// If it doesn't utilize the full underlying `ArrayBuffer`, it's sliced out of a pool.\r\n// \tif (buffer.byteOffset > 0) {\r\n// \t\treturn true\r\n// \t}\r\n//\r\n// \t// If it starts at `0`, but the underlying allocation is larger than the buffer itself, it's pooled/\r\n// \tif (buffer.buffer.byteLength > buffer.byteLength) {\r\n// \t\treturn true\r\n// \t}\r\n//\r\n// \treturn false\r\n// }\r\n\r\n// Concatenates chunks into a non-\"pooled\" buffer.\r\n//\r\n// It could've simply used `Buffer.concat(chunks)` to concatenate chunks into a buffer.\r\n// But, while using `Buffer.concat()` is simple, it results in a Node.js optimization\r\n// when it uses a \"shared\" memory region of about `8KB` to co-locate this array buffer\r\n// with other small array buffers if this array buffer is small too (i.e. <= `4KB`).\r\n//\r\n// Such co-location results in the inability to \"transfer\" this buffer\r\n// to a worker thread.\r\n//\r\n// A workaround is to manually allocate a separate memory region of a certain size.\r\n// This way, the resulting buffer will be non-\"pooled\".\r\n//\r\nfunction concatChunksToNonPooledBuffer(chunks) {\r\n\t// Calculate the total byte length of all the chunks combined.\r\n\tconst totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)\r\n\r\n\t// Allocate a non-pooled buffer.\r\n\t// Here it uses `Buffer.allocUnsafeSlow()` instead of the regular `Buffer.alloc()`.\r\n\t// The reason is that `Buffer.allocUnsafeSlow()` doesn't bother resetting the memory\r\n\t// that is being allocated, which is fine because it will be overwritten anyway.\r\n\t// It could also have used `Buffer.allocUnsafe()` if it wasn't returning pooled buffers,\r\n\t// which is what is being avoided here in the first place.\r\n\tconst buffer = Buffer.allocUnsafeSlow(totalLength)\r\n\r\n\t// Manually copy each chunk into the buffer.\r\n\tlet offset = 0\r\n\tfor (const chunk of chunks) {\r\n\t\tchunk.copy(buffer, offset)\r\n\t\toffset += chunk.length\r\n\t}\r\n\r\n\treturn buffer\r\n}"],"mappings":"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"}
@@ -9,55 +9,33 @@
9
9
  // * `unzipper` can completely skip a given compressed file entry if it's of no interest.
10
10
  // This means that a `.zip` archive could be read in multiple passes without performance penalty.
11
11
 
12
- import { Parse } from 'unzipper-esm';
13
- import { Buffer } from 'buffer';
12
+ import { Parse, ZipFileError } from 'unzipper-esm';
13
+ import { createUnzipError } from './UnzipError.js';
14
+ var PROMISE_RESOLVE_VALUE = undefined;
14
15
 
15
16
  /**
16
17
  * Reads `*.zip` file contents.
17
18
  * @param {Stream} stream
18
- * @return {Promise<Record<string,Buffer>>} Resolves to an object holding `*.zip` file entries. P.S. `Buffer` is a `Uint8Array`.
19
+ * @param {function} onFile
20
+ * @param {function} onFileData
21
+ * @param {function} onFileDataEnd
22
+ * @return {Promise<void>}
19
23
  */
20
- export default function unzipFromStream(stream) {
21
- var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
22
- filter = _ref.filter;
23
- // The `files` object stores the files and their contents.
24
- var files = {};
25
- var filesChunks = {};
26
- var onFile = function onFile(filePath) {
27
- // See if this file should be ignored.
28
- // If it should, this entry won't be processed, i.e. `Unzip` will not try
29
- // to decompress its data, and will just discard it.
30
- if (filter && !filter({
31
- path: filePath
32
- })) {
33
- return false;
34
- }
35
- filesChunks[filePath] = [];
36
- };
37
- var onFileData = function onFileData(filePath, chunk) {
38
- filesChunks[filePath].push(chunk);
39
- };
40
- var onFileDataEnd = function onFileDataEnd(filePath) {
41
- files[filePath] = Buffer.concat(filesChunks[filePath]);
42
- delete filesChunks[filePath];
43
- };
44
- return unzipFromStream_(stream, onFile, onFileData, onFileDataEnd).then(function () {
45
- return files;
46
- });
47
- }
48
- var PROMISE_RESOLVE_VALUE = undefined;
49
- function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {
24
+ export default function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {
50
25
  return new Promise(function (resolve, reject) {
51
26
  var promises = [];
52
27
  var errored = false;
53
28
  var onError = function onError(error) {
29
+ if (error instanceof ZipFileError) {
30
+ error = createUnzipError(error);
31
+ }
54
32
  if (!errored) {
55
33
  errored = true;
56
34
  reject(error);
57
35
  }
58
36
  };
59
37
  stream
60
- // This first "error" listener catches the original stream errors.
38
+ // This first "error" listener catches errors emitted from the input stream (for example, a file read error).
61
39
  //
62
40
  // That's because the .pipe() method does not automatically propagate errors
63
41
  // from a source (input) stream to the destination stream or the end of the pipeline.
@@ -101,7 +79,13 @@ function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {
101
79
  if (errored) {
102
80
  ignore = true;
103
81
  }
104
- if (onFile(entry.path) === false) {
82
+ // `entry.vars.uncompressedSize` will be `0` for `.zip` archives
83
+ // that were created in a streaming fashion.
84
+ // The `const fileSizeKnown = ...` code was copy-pasted from `node-unzipper` source code:
85
+ // https://github.com/ZJONSSON/node-unzipper/blob/master/lib/Open/unzip.js
86
+ var uncompressedFileSizeIsKnown = !(entry.vars.flags & 0x08) || entry.vars.compressedSize > 0;
87
+ var uncompressedFileSize = uncompressedFileSizeIsKnown ? entry.vars.uncompressedSize : undefined;
88
+ if (onFile(entry.path, uncompressedFileSize) === false) {
105
89
  ignore = true;
106
90
  }
107
91
 
@@ -1 +1 @@
1
- {"version":3,"file":"unzipFromStream.unzipper.js","names":["Parse","Buffer","unzipFromStream","stream","_ref","arguments","length","undefined","filter","files","filesChunks","onFile","filePath","path","onFileData","chunk","push","onFileDataEnd","concat","unzipFromStream_","then","PROMISE_RESOLVE_VALUE","Promise","resolve","reject","promises","errored","onError","error","on","pipe","all","entry","ignore","type","autodrain","data"],"sources":["../../source/zip/unzipFromStream.unzipper.js"],"sourcesContent":["// Considerations:\r\n//\r\n// * `unzipper` unpacks a `.zip` file using Node.js's \"native\" `zlib` module.\r\n// This means that it's more performant than any equivalent pure-javascript decompressor.\r\n//\r\n// * `unzipper` is a true \"streaming\" unpacker: it works in a \"pull\" fashion rather than a \"push\" one.\r\n// New chunks of archived data are only read and decompressed as the receiver is ready to process them.\r\n//\r\n// * `unzipper` can completely skip a given compressed file entry if it's of no interest.\r\n// This means that a `.zip` archive could be read in multiple passes without performance penalty.\r\n\r\nimport { Parse } from 'unzipper-esm'\r\n\r\nimport { Buffer } from 'buffer'\r\n\r\n/**\r\n * Reads `*.zip` file contents.\r\n * @param {Stream} stream\r\n * @return {Promise<Record<string,Buffer>>} Resolves to an object holding `*.zip` file entries. P.S. `Buffer` is a `Uint8Array`.\r\n */\r\nexport default function unzipFromStream(stream, { filter } = {}) {\r\n\t// The `files` object stores the files and their contents.\r\n\tconst files = {}\r\n\tconst filesChunks = {}\r\n\r\n\tconst onFile = (filePath) => {\r\n\t\t// See if this file should be ignored.\r\n\t\t// If it should, this entry won't be processed, i.e. `Unzip` will not try\r\n\t\t// to decompress its data, and will just discard it.\r\n\t\tif (filter && !filter({ path: filePath })) {\r\n\t\t\treturn false\r\n\t\t}\r\n\t\tfilesChunks[filePath] = []\r\n\t}\r\n\r\n\tconst onFileData = (filePath, chunk) => {\r\n\t\tfilesChunks[filePath].push(chunk)\r\n\t}\r\n\r\n\tconst onFileDataEnd = (filePath) => {\r\n\t\tfiles[filePath] = Buffer.concat(filesChunks[filePath])\r\n\t\tdelete filesChunks[filePath]\r\n\t}\r\n\r\n\treturn unzipFromStream_(stream, onFile, onFileData, onFileDataEnd).then(() => {\r\n\t\treturn files\r\n\t})\r\n}\r\n\r\nconst PROMISE_RESOLVE_VALUE = undefined\r\n\r\nfunction unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {\r\n\treturn new Promise((resolve, reject) => {\r\n\t\tconst promises = []\r\n\r\n\t\tlet errored = false\r\n\r\n\t\tconst onError = (error) => {\r\n\t\t\tif (!errored) {\r\n\t\t\t\terrored = true\r\n\t\t\t\treject(error)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tstream\r\n\t\t\t// This first \"error\" listener catches the original stream errors.\r\n\t\t\t//\r\n\t\t\t// That's because the .pipe() method does not automatically propagate errors\r\n\t\t\t// from a source (input) stream to the destination stream or the end of the pipeline.\r\n\t\t\t// You would need to attach an 'error' event handler to each stream in the chain.\r\n\t\t\t//\r\n\t\t\t// A more convenient alternative would be to use `stream.pipeline()` function:\r\n\t\t\t// `pipeline(stream1, stream2, (error) => { ... })`\r\n\t\t\t//\r\n\t\t\t.on('error', onError)\r\n\t\t\t// Pipe the input stream through the unzipper stream.\r\n\t\t\t.pipe(Parse())\r\n\t\t\t// This second \"error\" listener catches the unzipper stream errors.\r\n\t\t\t//\r\n\t\t\t// That's because the .pipe() method does not automatically propagate errors\r\n\t\t\t// from a source (input) stream to the destination stream or the end of the pipeline.\r\n\t\t\t// You would need to attach an 'error' event handler to each stream in the chain.\r\n\t\t\t//\r\n\t\t\t// A more convenient alternative would be to use `stream.pipeline()` function:\r\n\t\t\t// `pipeline(stream1, stream2, (error) => { ... })`\r\n\t\t\t//\r\n\t\t\t.on('error', onError)\r\n\t\t\t// The unzipper stream is closed when all `entries` have been reported.\r\n\t\t\t.on('finish', () => {\r\n\t\t\t\tif (!errored) {\r\n\t\t\t\t\t// Wait for all `entries` to be read.\r\n\t\t\t\t\t// The second argument of `.then()` function is not required\r\n\t\t\t\t\t// but I didn't remove it just to potentially prevent any potential silly bugs\r\n\t\t\t\t\t// in case of some potential changes in some potential future.\r\n\t\t\t\t\tPromise.all(promises).then(() => {\r\n\t\t\t\t\t\tresolve(PROMISE_RESOLVE_VALUE)\r\n\t\t\t\t\t}, onError)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t.on('entry', (entry) => {\r\n\t\t\t\t// See if this file should be ignored.\r\n\t\t\t\tlet ignore = false\r\n\t\t\t\t// `entry.type` could be 'Directory' or 'File'.\r\n\t\t\t\t// Ignore anything except files.\r\n\t\t\t\tif (entry.type === 'Directory') {\r\n\t\t\t\t\tignore = true\r\n\t\t\t\t}\r\n\t\t\t\tif (errored) {\r\n\t\t\t\t\tignore = true\r\n\t\t\t\t}\r\n\t\t\t\tif (onFile(entry.path) === false) {\r\n\t\t\t\t\tignore = true\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// If this file should be ignored.\r\n\t\t\t\tif (ignore) {\r\n\t\t\t\t\t// Call `entry.autodrain()` when you do not intend to process a specific `entry`'s raw data.\r\n\t\t\t\t\t// Otherwise, if an `entry` is not consumed (via .pipe(), .buffer(), or .autodrain()),\r\n\t\t\t\t\t// the stream will halt, preventing further file processing.\r\n\t\t\t\t\tentry.autodrain().on('error', onError)\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\r\n\t\t\t\tpromises.push(new Promise((resolve) => {\r\n\t\t\t\t\t// `entry` seems to be a generic Node.js stream.\r\n\t\t\t\t\t// `entry.pipe()` pipes the file contents to a stream.\r\n\t\t\t\t\t// `entry.stream()` returns a readable stream.\r\n\t\t\t\t\t// `entry.buffer()` returns a promise that resolves to a `Buffer` with the file contents.\r\n\t\t\t\t\tentry\r\n\t\t\t\t\t\t.on('data', (data) => {\r\n\t\t\t\t\t\t\tonFileData(entry.path, data)\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.on('error', (error) => {\r\n\t\t\t\t\t\t\tonError(error)\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.on('finish', () => {\r\n\t\t\t\t\t\t\tonFileDataEnd(entry.path)\r\n\t\t\t\t\t\t\tresolve()\r\n\t\t\t\t\t\t})\r\n\t\t\t\t}))\r\n\t\t\t})\r\n\t})\r\n}"],"mappings":"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"}
1
+ {"version":3,"file":"unzipFromStream.unzipper.js","names":["Parse","ZipFileError","createUnzipError","PROMISE_RESOLVE_VALUE","undefined","unzipFromStream_","stream","onFile","onFileData","onFileDataEnd","Promise","resolve","reject","promises","errored","onError","error","on","pipe","all","then","entry","ignore","type","uncompressedFileSizeIsKnown","vars","flags","compressedSize","uncompressedFileSize","uncompressedSize","path","autodrain","push","data"],"sources":["../../source/zip/unzipFromStream.unzipper.js"],"sourcesContent":["// Considerations:\r\n//\r\n// * `unzipper` unpacks a `.zip` file using Node.js's \"native\" `zlib` module.\r\n// This means that it's more performant than any equivalent pure-javascript decompressor.\r\n//\r\n// * `unzipper` is a true \"streaming\" unpacker: it works in a \"pull\" fashion rather than a \"push\" one.\r\n// New chunks of archived data are only read and decompressed as the receiver is ready to process them.\r\n//\r\n// * `unzipper` can completely skip a given compressed file entry if it's of no interest.\r\n// This means that a `.zip` archive could be read in multiple passes without performance penalty.\r\n\r\nimport { Parse, ZipFileError } from 'unzipper-esm'\r\n\r\nimport { createUnzipError } from './UnzipError.js'\r\n\r\nconst PROMISE_RESOLVE_VALUE = undefined\r\n\r\n/**\r\n * Reads `*.zip` file contents.\r\n * @param {Stream} stream\r\n * @param {function} onFile\r\n * @param {function} onFileData\r\n * @param {function} onFileDataEnd\r\n * @return {Promise<void>}\r\n */\r\nexport default function unzipFromStream_(stream, onFile, onFileData, onFileDataEnd) {\r\n\treturn new Promise((resolve, reject) => {\r\n\t\tconst promises = []\r\n\r\n\t\tlet errored = false\r\n\r\n\t\tconst onError = (error) => {\r\n\t\t\tif (error instanceof ZipFileError) {\r\n\t\t\t\terror = createUnzipError(error)\r\n\t\t\t}\r\n\t\t\tif (!errored) {\r\n\t\t\t\terrored = true\r\n\t\t\t\treject(error)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tstream\r\n\t\t\t// This first \"error\" listener catches errors emitted from the input stream (for example, a file read error).\r\n\t\t\t//\r\n\t\t\t// That's because the .pipe() method does not automatically propagate errors\r\n\t\t\t// from a source (input) stream to the destination stream or the end of the pipeline.\r\n\t\t\t// You would need to attach an 'error' event handler to each stream in the chain.\r\n\t\t\t//\r\n\t\t\t// A more convenient alternative would be to use `stream.pipeline()` function:\r\n\t\t\t// `pipeline(stream1, stream2, (error) => { ... })`\r\n\t\t\t//\r\n\t\t\t.on('error', onError)\r\n\t\t\t// Pipe the input stream through the unzipper stream.\r\n\t\t\t.pipe(Parse())\r\n\t\t\t// This second \"error\" listener catches the unzipper stream errors.\r\n\t\t\t//\r\n\t\t\t// That's because the .pipe() method does not automatically propagate errors\r\n\t\t\t// from a source (input) stream to the destination stream or the end of the pipeline.\r\n\t\t\t// You would need to attach an 'error' event handler to each stream in the chain.\r\n\t\t\t//\r\n\t\t\t// A more convenient alternative would be to use `stream.pipeline()` function:\r\n\t\t\t// `pipeline(stream1, stream2, (error) => { ... })`\r\n\t\t\t//\r\n\t\t\t.on('error', onError)\r\n\t\t\t// The unzipper stream is closed when all `entries` have been reported.\r\n\t\t\t.on('finish', () => {\r\n\t\t\t\tif (!errored) {\r\n\t\t\t\t\t// Wait for all `entries` to be read.\r\n\t\t\t\t\t// The second argument of `.then()` function is not required\r\n\t\t\t\t\t// but I didn't remove it just to potentially prevent any potential silly bugs\r\n\t\t\t\t\t// in case of some potential changes in some potential future.\r\n\t\t\t\t\tPromise.all(promises).then(() => {\r\n\t\t\t\t\t\tresolve(PROMISE_RESOLVE_VALUE)\r\n\t\t\t\t\t}, onError)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t.on('entry', (entry) => {\r\n\t\t\t\t// See if this file should be ignored.\r\n\t\t\t\tlet ignore = false\r\n\t\t\t\t// `entry.type` could be 'Directory' or 'File'.\r\n\t\t\t\t// Ignore anything except files.\r\n\t\t\t\tif (entry.type === 'Directory') {\r\n\t\t\t\t\tignore = true\r\n\t\t\t\t}\r\n\t\t\t\tif (errored) {\r\n\t\t\t\t\tignore = true\r\n\t\t\t\t}\r\n\t\t\t\t// `entry.vars.uncompressedSize` will be `0` for `.zip` archives\r\n\t\t\t\t// that were created in a streaming fashion.\r\n\t\t\t\t// The `const fileSizeKnown = ...` code was copy-pasted from `node-unzipper` source code:\r\n\t\t\t\t// https://github.com/ZJONSSON/node-unzipper/blob/master/lib/Open/unzip.js\r\n\t\t\t\tconst uncompressedFileSizeIsKnown = !(entry.vars.flags & 0x08) || entry.vars.compressedSize > 0\r\n\t\t\t\tconst uncompressedFileSize = uncompressedFileSizeIsKnown ? entry.vars.uncompressedSize : undefined\r\n\t\t\t\tif (onFile(entry.path, uncompressedFileSize) === false) {\r\n\t\t\t\t\tignore = true\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// If this file should be ignored.\r\n\t\t\t\tif (ignore) {\r\n\t\t\t\t\t// Call `entry.autodrain()` when you do not intend to process a specific `entry`'s raw data.\r\n\t\t\t\t\t// Otherwise, if an `entry` is not consumed (via .pipe(), .buffer(), or .autodrain()),\r\n\t\t\t\t\t// the stream will halt, preventing further file processing.\r\n\t\t\t\t\tentry.autodrain().on('error', onError)\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\r\n\t\t\t\tpromises.push(new Promise((resolve) => {\r\n\t\t\t\t\t// `entry` seems to be a generic Node.js stream.\r\n\t\t\t\t\t// `entry.pipe()` pipes the file contents to a stream.\r\n\t\t\t\t\t// `entry.stream()` returns a readable stream.\r\n\t\t\t\t\t// `entry.buffer()` returns a promise that resolves to a `Buffer` with the file contents.\r\n\t\t\t\t\tentry\r\n\t\t\t\t\t\t.on('data', (data) => {\r\n\t\t\t\t\t\t\tonFileData(entry.path, data)\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.on('error', (error) => {\r\n\t\t\t\t\t\t\tonError(error)\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.on('finish', () => {\r\n\t\t\t\t\t\t\tonFileDataEnd(entry.path)\r\n\t\t\t\t\t\t\tresolve()\r\n\t\t\t\t\t\t})\r\n\t\t\t\t}))\r\n\t\t\t})\r\n\t})\r\n}"],"mappings":"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"}
package/node/index.cjs CHANGED
@@ -2,6 +2,9 @@ exports = module.exports = require('../commonjs/export/readXlsxFileNode.js').def
2
2
  exports['default'] = require('../commonjs/export/readXlsxFileNode.js').default
3
3
 
4
4
  exports.readSheet = require('../commonjs/export/readSheetNode.js').default
5
+ exports.InvalidInputError = require('../commonjs/xlsx/file/InvalidInputError.js').default
6
+ exports.InvalidSpreadsheetError = require('../commonjs/xlsx/InvalidSpreadsheetError.js').default
7
+ exports.SheetNotFoundError = require('../commonjs/xlsx/SheetNotFoundError.js').default
5
8
 
6
9
  // `parseSheetData()`
7
10
  exports.parseSheetData = require('../commonjs/parseSheetData/parseSheetData.js').default
package/node/index.d.ts CHANGED
@@ -70,6 +70,22 @@ export type {
70
70
  Schema
71
71
  } from '../types/parseSheetData/parseSheetDataSchema.d.js'
72
72
 
73
+ export {
74
+ InvalidInputError
75
+ } from '../types/InvalidInputError.d.js'
76
+
77
+ export type {
78
+ InvalidInputErrorCode
79
+ } from '../types/InvalidInputError.d.js'
80
+
81
+ export {
82
+ InvalidSpreadsheetError
83
+ } from '../types/InvalidSpreadsheetError.d.js'
84
+
85
+ export {
86
+ SheetNotFoundError
87
+ } from '../types/SheetNotFoundError.d.js'
88
+
73
89
  export default function readXlsxFile<ParsedNumber = number>(
74
90
  input: Input,
75
91
  options?: Options<ParsedNumber>
package/node/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  export { default as default } from '../modules/export/readXlsxFileNode.js'
2
2
  export { default as readSheet } from '../modules/export/readSheetNode.js'
3
+ export { default as InvalidInputError } from '../modules/xlsx/file/InvalidInputError.js'
4
+ export { default as InvalidSpreadsheetError } from '../modules/xlsx/InvalidSpreadsheetError.js'
5
+ export { default as SheetNotFoundError } from '../modules/xlsx/SheetNotFoundError.js'
3
6
 
4
7
  // `parseSheetData()`
5
8
  export { default as parseSheetData } from '../modules/parseSheetData/parseSheetData.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "read-excel-file",
3
- "version": "9.3.4",
3
+ "version": "9.3.5",
4
4
  "description": "Read `.xlsx` files in a web browser or in Node.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -46,8 +46,9 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "fflate": "^0.8.3",
49
- "unzipper-esm": "^0.13.2",
50
- "saxen": "^11.0.2"
49
+ "saxen": "^11.1.0",
50
+ "unzipper-esm": "^0.13.3",
51
+ "worker-f": "^0.1.12"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@babel/cli": "^7.17.10",
@@ -59,6 +60,7 @@
59
60
  "chai": "^4.3.6",
60
61
  "core-js": "^3.22.5",
61
62
  "cross-env": "^10.1.0",
63
+ "libphonenumber-js": "^1.13.8",
62
64
  "mocha": "^10.0.0",
63
65
  "npm-run-all": "^4.1.5",
64
66
  "rimraf": "^6.1.3",