@yuanliwei/exceljs 4.4.0

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 (185) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +3024 -0
  3. package/README_zh.md +2878 -0
  4. package/excel.js +13 -0
  5. package/index.d.ts +2040 -0
  6. package/index.ts +2 -0
  7. package/lib/csv/csv.js +191 -0
  8. package/lib/csv/line-buffer.js +74 -0
  9. package/lib/csv/stream-converter.js +135 -0
  10. package/lib/doc/anchor.js +91 -0
  11. package/lib/doc/cell.js +1124 -0
  12. package/lib/doc/column.js +320 -0
  13. package/lib/doc/data/theme1.json +234 -0
  14. package/lib/doc/data-validations.js +19 -0
  15. package/lib/doc/defined-names.js +196 -0
  16. package/lib/doc/enums.js +48 -0
  17. package/lib/doc/image.js +59 -0
  18. package/lib/doc/modelcontainer.js +18 -0
  19. package/lib/doc/note.js +65 -0
  20. package/lib/doc/pivot-table.js +132 -0
  21. package/lib/doc/range.js +257 -0
  22. package/lib/doc/row.js +415 -0
  23. package/lib/doc/table.js +465 -0
  24. package/lib/doc/workbook.js +224 -0
  25. package/lib/doc/worksheet.js +949 -0
  26. package/lib/exceljs.bare.js +13 -0
  27. package/lib/exceljs.browser.js +36 -0
  28. package/lib/exceljs.nodejs.js +14 -0
  29. package/lib/stream/xlsx/hyperlink-reader.js +83 -0
  30. package/lib/stream/xlsx/sheet-comments-writer.js +121 -0
  31. package/lib/stream/xlsx/sheet-rels-writer.js +119 -0
  32. package/lib/stream/xlsx/workbook-reader.js +337 -0
  33. package/lib/stream/xlsx/workbook-writer.js +347 -0
  34. package/lib/stream/xlsx/worksheet-reader.js +374 -0
  35. package/lib/stream/xlsx/worksheet-writer.js +717 -0
  36. package/lib/utils/auto-drain.js +15 -0
  37. package/lib/utils/browser-buffer-decode.js +14 -0
  38. package/lib/utils/browser-buffer-encode.js +15 -0
  39. package/lib/utils/cell-matrix.js +165 -0
  40. package/lib/utils/col-cache.js +287 -0
  41. package/lib/utils/copy-style.js +43 -0
  42. package/lib/utils/encryptor.js +55 -0
  43. package/lib/utils/iterate-stream.js +48 -0
  44. package/lib/utils/parse-sax.js +30 -0
  45. package/lib/utils/shared-formula.js +44 -0
  46. package/lib/utils/shared-strings.js +35 -0
  47. package/lib/utils/stream-base64.js +72 -0
  48. package/lib/utils/stream-buf.js +364 -0
  49. package/lib/utils/string-buf.js +82 -0
  50. package/lib/utils/string-builder.js +35 -0
  51. package/lib/utils/stuttered-pipe.js +67 -0
  52. package/lib/utils/typed-stack.js +24 -0
  53. package/lib/utils/under-dash.js +184 -0
  54. package/lib/utils/utils.js +205 -0
  55. package/lib/utils/xml-stream.js +169 -0
  56. package/lib/utils/zip-stream.js +87 -0
  57. package/lib/xlsx/.rels +11 -0
  58. package/lib/xlsx/calcChain.xml +6 -0
  59. package/lib/xlsx/core.xml +7 -0
  60. package/lib/xlsx/defaultnumformats.js +153 -0
  61. package/lib/xlsx/rel-type.js +20 -0
  62. package/lib/xlsx/styles.xml +41 -0
  63. package/lib/xlsx/workbook.xml +16 -0
  64. package/lib/xlsx/xform/base-xform.js +145 -0
  65. package/lib/xlsx/xform/book/defined-name-xform.js +91 -0
  66. package/lib/xlsx/xform/book/sheet-xform.js +34 -0
  67. package/lib/xlsx/xform/book/workbook-calc-properties-xform.js +26 -0
  68. package/lib/xlsx/xform/book/workbook-pivot-cache-xform.js +29 -0
  69. package/lib/xlsx/xform/book/workbook-properties-xform.js +29 -0
  70. package/lib/xlsx/xform/book/workbook-view-xform.js +53 -0
  71. package/lib/xlsx/xform/book/workbook-xform.js +259 -0
  72. package/lib/xlsx/xform/comment/comment-xform.js +105 -0
  73. package/lib/xlsx/xform/comment/comments-xform.js +82 -0
  74. package/lib/xlsx/xform/comment/style/vml-position-xform.js +39 -0
  75. package/lib/xlsx/xform/comment/style/vml-protection-xform.js +36 -0
  76. package/lib/xlsx/xform/comment/vml-anchor-xform.js +60 -0
  77. package/lib/xlsx/xform/comment/vml-client-data-xform.js +95 -0
  78. package/lib/xlsx/xform/comment/vml-notes-xform.js +107 -0
  79. package/lib/xlsx/xform/comment/vml-shape-xform.js +95 -0
  80. package/lib/xlsx/xform/comment/vml-textbox-xform.js +64 -0
  81. package/lib/xlsx/xform/composite-xform.js +56 -0
  82. package/lib/xlsx/xform/core/app-heading-pairs-xform.js +32 -0
  83. package/lib/xlsx/xform/core/app-titles-of-parts-xform.js +28 -0
  84. package/lib/xlsx/xform/core/app-xform.js +100 -0
  85. package/lib/xlsx/xform/core/content-types-xform.js +135 -0
  86. package/lib/xlsx/xform/core/core-xform.js +136 -0
  87. package/lib/xlsx/xform/core/relationship-xform.js +25 -0
  88. package/lib/xlsx/xform/core/relationships-xform.js +73 -0
  89. package/lib/xlsx/xform/drawing/base-cell-anchor-xform.js +48 -0
  90. package/lib/xlsx/xform/drawing/blip-fill-xform.js +71 -0
  91. package/lib/xlsx/xform/drawing/blip-xform.js +42 -0
  92. package/lib/xlsx/xform/drawing/c-nv-pic-pr-xform.js +38 -0
  93. package/lib/xlsx/xform/drawing/c-nv-pr-xform.js +68 -0
  94. package/lib/xlsx/xform/drawing/cell-position-xform.js +77 -0
  95. package/lib/xlsx/xform/drawing/drawing-xform.js +109 -0
  96. package/lib/xlsx/xform/drawing/ext-lst-xform.js +43 -0
  97. package/lib/xlsx/xform/drawing/ext-xform.js +44 -0
  98. package/lib/xlsx/xform/drawing/hlink-click-xform.js +41 -0
  99. package/lib/xlsx/xform/drawing/nv-pic-pr-xform.js +65 -0
  100. package/lib/xlsx/xform/drawing/one-cell-anchor-xform.js +63 -0
  101. package/lib/xlsx/xform/drawing/pic-xform.js +77 -0
  102. package/lib/xlsx/xform/drawing/sp-pr.js +17 -0
  103. package/lib/xlsx/xform/drawing/two-cell-anchor-xform.js +62 -0
  104. package/lib/xlsx/xform/list-xform.js +95 -0
  105. package/lib/xlsx/xform/pivot-table/cache-field.js +43 -0
  106. package/lib/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +77 -0
  107. package/lib/xlsx/xform/pivot-table/pivot-cache-records-xform.js +103 -0
  108. package/lib/xlsx/xform/pivot-table/pivot-table-xform.js +189 -0
  109. package/lib/xlsx/xform/sheet/auto-filter-xform.js +38 -0
  110. package/lib/xlsx/xform/sheet/cell-xform.js +498 -0
  111. package/lib/xlsx/xform/sheet/cf/cf-rule-xform.js +301 -0
  112. package/lib/xlsx/xform/sheet/cf/cfvo-xform.js +27 -0
  113. package/lib/xlsx/xform/sheet/cf/color-scale-xform.js +45 -0
  114. package/lib/xlsx/xform/sheet/cf/conditional-formatting-xform.js +48 -0
  115. package/lib/xlsx/xform/sheet/cf/conditional-formattings-xform.js +92 -0
  116. package/lib/xlsx/xform/sheet/cf/databar-xform.js +49 -0
  117. package/lib/xlsx/xform/sheet/cf/ext-lst-ref-xform.js +87 -0
  118. package/lib/xlsx/xform/sheet/cf/formula-xform.js +25 -0
  119. package/lib/xlsx/xform/sheet/cf/icon-set-xform.js +47 -0
  120. package/lib/xlsx/xform/sheet/cf-ext/cf-icon-ext-xform.js +27 -0
  121. package/lib/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +98 -0
  122. package/lib/xlsx/xform/sheet/cf-ext/cfvo-ext-xform.js +43 -0
  123. package/lib/xlsx/xform/sheet/cf-ext/conditional-formatting-ext-xform.js +62 -0
  124. package/lib/xlsx/xform/sheet/cf-ext/conditional-formattings-ext-xform.js +50 -0
  125. package/lib/xlsx/xform/sheet/cf-ext/databar-ext-xform.js +98 -0
  126. package/lib/xlsx/xform/sheet/cf-ext/f-ext-xform.js +25 -0
  127. package/lib/xlsx/xform/sheet/cf-ext/icon-set-ext-xform.js +73 -0
  128. package/lib/xlsx/xform/sheet/cf-ext/sqref-ext-xform.js +25 -0
  129. package/lib/xlsx/xform/sheet/col-xform.js +86 -0
  130. package/lib/xlsx/xform/sheet/data-validations-xform.js +257 -0
  131. package/lib/xlsx/xform/sheet/dimension-xform.js +29 -0
  132. package/lib/xlsx/xform/sheet/drawing-xform.js +33 -0
  133. package/lib/xlsx/xform/sheet/ext-lst-xform.js +86 -0
  134. package/lib/xlsx/xform/sheet/header-footer-xform.js +146 -0
  135. package/lib/xlsx/xform/sheet/hyperlink-xform.js +54 -0
  136. package/lib/xlsx/xform/sheet/merge-cell-xform.js +27 -0
  137. package/lib/xlsx/xform/sheet/merges.js +56 -0
  138. package/lib/xlsx/xform/sheet/outline-properties-xform.js +43 -0
  139. package/lib/xlsx/xform/sheet/page-breaks-xform.js +27 -0
  140. package/lib/xlsx/xform/sheet/page-margins-xform.js +49 -0
  141. package/lib/xlsx/xform/sheet/page-setup-properties-xform.js +35 -0
  142. package/lib/xlsx/xform/sheet/page-setup-xform.js +103 -0
  143. package/lib/xlsx/xform/sheet/picture-xform.js +33 -0
  144. package/lib/xlsx/xform/sheet/print-options-xform.js +49 -0
  145. package/lib/xlsx/xform/sheet/row-breaks-xform.js +39 -0
  146. package/lib/xlsx/xform/sheet/row-xform.js +142 -0
  147. package/lib/xlsx/xform/sheet/sheet-format-properties-xform.js +55 -0
  148. package/lib/xlsx/xform/sheet/sheet-properties-xform.js +90 -0
  149. package/lib/xlsx/xform/sheet/sheet-protection-xform.js +89 -0
  150. package/lib/xlsx/xform/sheet/sheet-view-xform.js +202 -0
  151. package/lib/xlsx/xform/sheet/table-part-xform.js +33 -0
  152. package/lib/xlsx/xform/sheet/worksheet-xform.js +548 -0
  153. package/lib/xlsx/xform/simple/boolean-xform.js +31 -0
  154. package/lib/xlsx/xform/simple/date-xform.js +66 -0
  155. package/lib/xlsx/xform/simple/float-xform.js +51 -0
  156. package/lib/xlsx/xform/simple/integer-xform.js +57 -0
  157. package/lib/xlsx/xform/simple/string-xform.js +51 -0
  158. package/lib/xlsx/xform/static-xform.js +64 -0
  159. package/lib/xlsx/xform/strings/phonetic-text-xform.js +98 -0
  160. package/lib/xlsx/xform/strings/rich-text-xform.js +101 -0
  161. package/lib/xlsx/xform/strings/shared-string-xform.js +102 -0
  162. package/lib/xlsx/xform/strings/shared-strings-xform.js +127 -0
  163. package/lib/xlsx/xform/strings/text-xform.js +44 -0
  164. package/lib/xlsx/xform/style/alignment-xform.js +172 -0
  165. package/lib/xlsx/xform/style/border-xform.js +207 -0
  166. package/lib/xlsx/xform/style/color-xform.js +63 -0
  167. package/lib/xlsx/xform/style/dxf-xform.js +111 -0
  168. package/lib/xlsx/xform/style/fill-xform.js +364 -0
  169. package/lib/xlsx/xform/style/font-xform.js +102 -0
  170. package/lib/xlsx/xform/style/numfmt-xform.js +63 -0
  171. package/lib/xlsx/xform/style/protection-xform.js +60 -0
  172. package/lib/xlsx/xform/style/style-xform.js +125 -0
  173. package/lib/xlsx/xform/style/styles-xform.js +527 -0
  174. package/lib/xlsx/xform/style/underline-xform.js +47 -0
  175. package/lib/xlsx/xform/table/auto-filter-xform.js +81 -0
  176. package/lib/xlsx/xform/table/custom-filter-xform.js +33 -0
  177. package/lib/xlsx/xform/table/filter-column-xform.js +96 -0
  178. package/lib/xlsx/xform/table/filter-xform.js +31 -0
  179. package/lib/xlsx/xform/table/table-column-xform.js +44 -0
  180. package/lib/xlsx/xform/table/table-style-info-xform.js +41 -0
  181. package/lib/xlsx/xform/table/table-xform.js +131 -0
  182. package/lib/xlsx/xlsx.js +774 -0
  183. package/lib/xlsx/xml/theme1.js +3 -0
  184. package/lib/xlsx/xml/theme1.xml +318 -0
  185. package/package.json +149 -0
@@ -0,0 +1,337 @@
1
+ const fs = require('fs');
2
+ const {EventEmitter} = require('events');
3
+ const {PassThrough, Readable} = require('readable-stream');
4
+ const nodeStream = require('stream');
5
+ const unzip = require('unzipper');
6
+ const tmp = require('tmp');
7
+ const iterateStream = require('../../utils/iterate-stream');
8
+ const parseSax = require('../../utils/parse-sax');
9
+
10
+ const StyleManager = require('../../xlsx/xform/style/styles-xform');
11
+ const WorkbookXform = require('../../xlsx/xform/book/workbook-xform');
12
+ const RelationshipsXform = require('../../xlsx/xform/core/relationships-xform');
13
+
14
+ const WorksheetReader = require('./worksheet-reader');
15
+ const HyperlinkReader = require('./hyperlink-reader');
16
+
17
+ tmp.setGracefulCleanup();
18
+
19
+ class WorkbookReader extends EventEmitter {
20
+ constructor(input, options = {}) {
21
+ super();
22
+
23
+ this.input = input;
24
+
25
+ this.options = {
26
+ worksheets: 'emit',
27
+ sharedStrings: 'cache',
28
+ hyperlinks: 'ignore',
29
+ styles: 'ignore',
30
+ entries: 'ignore',
31
+ ...options,
32
+ };
33
+
34
+ this.styles = new StyleManager();
35
+ this.styles.init();
36
+ }
37
+
38
+ _getStream(input) {
39
+ if (input instanceof nodeStream.Readable || input instanceof Readable) {
40
+ return input;
41
+ }
42
+ if (typeof input === 'string') {
43
+ return fs.createReadStream(input);
44
+ }
45
+ throw new Error(`Could not recognise input: ${input}`);
46
+ }
47
+
48
+ async read(input, options) {
49
+ try {
50
+ for await (const {eventType, value} of this.parse(input, options)) {
51
+ switch (eventType) {
52
+ case 'shared-strings':
53
+ this.emit(eventType, value);
54
+ break;
55
+ case 'worksheet':
56
+ this.emit(eventType, value);
57
+ await value.read();
58
+ break;
59
+ case 'hyperlinks':
60
+ this.emit(eventType, value);
61
+ break;
62
+ }
63
+ }
64
+ this.emit('end');
65
+ this.emit('finished');
66
+ } catch (error) {
67
+ this.emit('error', error);
68
+ }
69
+ }
70
+
71
+ async *[Symbol.asyncIterator]() {
72
+ for await (const {eventType, value} of this.parse()) {
73
+ if (eventType === 'worksheet') {
74
+ yield value;
75
+ }
76
+ }
77
+ }
78
+
79
+ async *parse(input, options) {
80
+ if (options) this.options = options;
81
+ const stream = (this.stream = this._getStream(input || this.input));
82
+ const zip = unzip.Parse({forceStream: true});
83
+ stream.pipe(zip);
84
+
85
+ // worksheets, deferred for parsing after shared strings reading
86
+ const waitingWorkSheets = [];
87
+
88
+ for await (const entry of iterateStream(zip)) {
89
+ let match;
90
+ let sheetNo;
91
+ switch (entry.path) {
92
+ case '_rels/.rels':
93
+ break;
94
+ case 'xl/_rels/workbook.xml.rels':
95
+ await this._parseRels(entry);
96
+ break;
97
+ case 'xl/workbook.xml':
98
+ await this._parseWorkbook(entry);
99
+ break;
100
+ case 'xl/sharedStrings.xml':
101
+ yield* this._parseSharedStrings(entry);
102
+ break;
103
+ case 'xl/styles.xml':
104
+ await this._parseStyles(entry);
105
+ break;
106
+ default:
107
+ if (entry.path.match(/xl\/worksheets\/sheet\d+[.]xml/)) {
108
+ match = entry.path.match(/xl\/worksheets\/sheet(\d+)[.]xml/);
109
+ sheetNo = match[1];
110
+ if (this.sharedStrings && this.workbookRels) {
111
+ yield* this._parseWorksheet(iterateStream(entry), sheetNo);
112
+ } else {
113
+ // create temp file for each worksheet
114
+ await new Promise((resolve, reject) => {
115
+ tmp.file((err, path, fd, tempFileCleanupCallback) => {
116
+ if (err) {
117
+ return reject(err);
118
+ }
119
+ waitingWorkSheets.push({sheetNo, path, tempFileCleanupCallback});
120
+
121
+ const tempStream = fs.createWriteStream(path);
122
+ tempStream.on('error', reject);
123
+ entry.pipe(tempStream);
124
+ return tempStream.on('finish', () => {
125
+ return resolve();
126
+ });
127
+ });
128
+ });
129
+ }
130
+ } else if (entry.path.match(/xl\/worksheets\/_rels\/sheet\d+[.]xml.rels/)) {
131
+ match = entry.path.match(/xl\/worksheets\/_rels\/sheet(\d+)[.]xml.rels/);
132
+ sheetNo = match[1];
133
+ yield* this._parseHyperlinks(iterateStream(entry), sheetNo);
134
+ }
135
+ break;
136
+ }
137
+ entry.autodrain();
138
+ }
139
+
140
+ for (const {sheetNo, path, tempFileCleanupCallback} of waitingWorkSheets) {
141
+ let fileStream = fs.createReadStream(path);
142
+ // TODO: Remove once node v8 is deprecated
143
+ // Detect and upgrade old fileStreams
144
+ if (!fileStream[Symbol.asyncIterator]) {
145
+ fileStream = fileStream.pipe(new PassThrough());
146
+ }
147
+ yield* this._parseWorksheet(fileStream, sheetNo);
148
+ tempFileCleanupCallback();
149
+ }
150
+ }
151
+
152
+ _emitEntry(payload) {
153
+ if (this.options.entries === 'emit') {
154
+ this.emit('entry', payload);
155
+ }
156
+ }
157
+
158
+ async _parseRels(entry) {
159
+ const xform = new RelationshipsXform();
160
+ this.workbookRels = await xform.parseStream(iterateStream(entry));
161
+ }
162
+
163
+ async _parseWorkbook(entry) {
164
+ this._emitEntry({type: 'workbook'});
165
+
166
+ const workbook = new WorkbookXform();
167
+ await workbook.parseStream(iterateStream(entry));
168
+
169
+ this.properties = workbook.map.workbookPr;
170
+ this.model = workbook.model;
171
+ }
172
+
173
+ async *_parseSharedStrings(entry) {
174
+ this._emitEntry({type: 'shared-strings'});
175
+ switch (this.options.sharedStrings) {
176
+ case 'cache':
177
+ this.sharedStrings = [];
178
+ break;
179
+ case 'emit':
180
+ break;
181
+ default:
182
+ return;
183
+ }
184
+
185
+ let text = null;
186
+ let richText = [];
187
+ let index = 0;
188
+ let font = null;
189
+ for await (const events of parseSax(iterateStream(entry))) {
190
+ for (const {eventType, value} of events) {
191
+ if (eventType === 'opentag') {
192
+ const node = value;
193
+ switch (node.name) {
194
+ case 'b':
195
+ font = font || {};
196
+ font.bold = true;
197
+ break;
198
+ case 'charset':
199
+ font = font || {};
200
+ font.charset = parseInt(node.attributes.charset, 10);
201
+ break;
202
+ case 'color':
203
+ font = font || {};
204
+ font.color = {};
205
+ if (node.attributes.rgb) {
206
+ font.color.argb = node.attributes.argb;
207
+ }
208
+ if (node.attributes.val) {
209
+ font.color.argb = node.attributes.val;
210
+ }
211
+ if (node.attributes.theme) {
212
+ font.color.theme = node.attributes.theme;
213
+ }
214
+ break;
215
+ case 'family':
216
+ font = font || {};
217
+ font.family = parseInt(node.attributes.val, 10);
218
+ break;
219
+ case 'i':
220
+ font = font || {};
221
+ font.italic = true;
222
+ break;
223
+ case 'outline':
224
+ font = font || {};
225
+ font.outline = true;
226
+ break;
227
+ case 'rFont':
228
+ font = font || {};
229
+ font.name = node.value;
230
+ break;
231
+ case 'si':
232
+ font = null;
233
+ richText = [];
234
+ text = null;
235
+ break;
236
+ case 'sz':
237
+ font = font || {};
238
+ font.size = parseInt(node.attributes.val, 10);
239
+ break;
240
+ case 'strike':
241
+ break;
242
+ case 't':
243
+ text = null;
244
+ break;
245
+ case 'u':
246
+ font = font || {};
247
+ font.underline = true;
248
+ break;
249
+ case 'vertAlign':
250
+ font = font || {};
251
+ font.vertAlign = node.attributes.val;
252
+ break;
253
+ }
254
+ } else if (eventType === 'text') {
255
+ text = text ? text + value : value;
256
+ } else if (eventType === 'closetag') {
257
+ const node = value;
258
+ switch (node.name) {
259
+ case 'r':
260
+ richText.push({
261
+ font,
262
+ text,
263
+ });
264
+
265
+ font = null;
266
+ text = null;
267
+ break;
268
+ case 'si':
269
+ if (this.options.sharedStrings === 'cache') {
270
+ this.sharedStrings.push(richText.length ? {richText} : text);
271
+ } else if (this.options.sharedStrings === 'emit') {
272
+ yield {index: index++, text: richText.length ? {richText} : text};
273
+ }
274
+
275
+ richText = [];
276
+ font = null;
277
+ text = null;
278
+ break;
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
284
+
285
+ async _parseStyles(entry) {
286
+ this._emitEntry({type: 'styles'});
287
+ if (this.options.styles === 'cache') {
288
+ this.styles = new StyleManager();
289
+ await this.styles.parseStream(iterateStream(entry));
290
+ }
291
+ }
292
+
293
+ *_parseWorksheet(iterator, sheetNo) {
294
+ this._emitEntry({type: 'worksheet', id: sheetNo});
295
+ const worksheetReader = new WorksheetReader({
296
+ workbook: this,
297
+ id: sheetNo,
298
+ iterator,
299
+ options: this.options,
300
+ });
301
+
302
+ const matchingRel = (this.workbookRels || []).find(rel => rel.Target === `worksheets/sheet${sheetNo}.xml`);
303
+ const matchingSheet = matchingRel && (this.model.sheets || []).find(sheet => sheet.rId === matchingRel.Id);
304
+ if (matchingSheet) {
305
+ worksheetReader.id = matchingSheet.id;
306
+ worksheetReader.name = matchingSheet.name;
307
+ worksheetReader.state = matchingSheet.state;
308
+ }
309
+ if (this.options.worksheets === 'emit') {
310
+ yield {eventType: 'worksheet', value: worksheetReader};
311
+ }
312
+ }
313
+
314
+ *_parseHyperlinks(iterator, sheetNo) {
315
+ this._emitEntry({type: 'hyperlinks', id: sheetNo});
316
+ const hyperlinksReader = new HyperlinkReader({
317
+ workbook: this,
318
+ id: sheetNo,
319
+ iterator,
320
+ options: this.options,
321
+ });
322
+ if (this.options.hyperlinks === 'emit') {
323
+ yield {eventType: 'hyperlinks', value: hyperlinksReader};
324
+ }
325
+ }
326
+ }
327
+
328
+ // for reference - these are the valid values for options
329
+ WorkbookReader.Options = {
330
+ worksheets: ['emit', 'ignore'],
331
+ sharedStrings: ['cache', 'emit', 'ignore'],
332
+ hyperlinks: ['cache', 'emit', 'ignore'],
333
+ styles: ['cache', 'ignore'],
334
+ entries: ['emit', 'ignore'],
335
+ };
336
+
337
+ module.exports = WorkbookReader;
@@ -0,0 +1,347 @@
1
+ const fs = require('fs');
2
+ const Archiver = require('archiver');
3
+
4
+ const StreamBuf = require('../../utils/stream-buf');
5
+
6
+ const RelType = require('../../xlsx/rel-type');
7
+ const StylesXform = require('../../xlsx/xform/style/styles-xform');
8
+ const SharedStrings = require('../../utils/shared-strings');
9
+ const DefinedNames = require('../../doc/defined-names');
10
+
11
+ const CoreXform = require('../../xlsx/xform/core/core-xform');
12
+ const RelationshipsXform = require('../../xlsx/xform/core/relationships-xform');
13
+ const ContentTypesXform = require('../../xlsx/xform/core/content-types-xform');
14
+ const AppXform = require('../../xlsx/xform/core/app-xform');
15
+ const WorkbookXform = require('../../xlsx/xform/book/workbook-xform');
16
+ const SharedStringsXform = require('../../xlsx/xform/strings/shared-strings-xform');
17
+
18
+ const WorksheetWriter = require('./worksheet-writer');
19
+
20
+ const theme1Xml = require('../../xlsx/xml/theme1.js');
21
+
22
+ class WorkbookWriter {
23
+ constructor(options) {
24
+ options = options || {};
25
+
26
+ this.created = options.created || new Date();
27
+ this.modified = options.modified || this.created;
28
+ this.creator = options.creator || 'ExcelJS';
29
+ this.lastModifiedBy = options.lastModifiedBy || 'ExcelJS';
30
+ this.lastPrinted = options.lastPrinted;
31
+
32
+ // using shared strings creates a smaller xlsx file but may use more memory
33
+ this.useSharedStrings = options.useSharedStrings || false;
34
+ this.sharedStrings = new SharedStrings();
35
+
36
+ // style manager
37
+ this.styles = options.useStyles ? new StylesXform(true) : new StylesXform.Mock(true);
38
+
39
+ // defined names
40
+ this._definedNames = new DefinedNames();
41
+
42
+ this._worksheets = [];
43
+ this.views = [];
44
+
45
+ this.zipOptions = options.zip;
46
+
47
+ this.media = [];
48
+ this.commentRefs = [];
49
+
50
+ this.zip = Archiver('zip', this.zipOptions);
51
+ if (options.stream) {
52
+ this.stream = options.stream;
53
+ } else if (options.filename) {
54
+ this.stream = fs.createWriteStream(options.filename);
55
+ } else {
56
+ this.stream = new StreamBuf();
57
+ }
58
+ this.zip.pipe(this.stream);
59
+
60
+ // these bits can be added right now
61
+ this.promise = Promise.all([this.addThemes(), this.addOfficeRels()]);
62
+ }
63
+
64
+ get definedNames() {
65
+ return this._definedNames;
66
+ }
67
+
68
+ _openStream(path) {
69
+ const stream = new StreamBuf({bufSize: 65536, batch: true});
70
+ this.zip.append(stream, {name: path});
71
+ stream.on('finish', () => {
72
+ stream.emit('zipped');
73
+ });
74
+ return stream;
75
+ }
76
+
77
+ _commitWorksheets() {
78
+ const commitWorksheet = function(worksheet) {
79
+ if (!worksheet.committed) {
80
+ return new Promise(resolve => {
81
+ worksheet.stream.on('zipped', () => {
82
+ resolve();
83
+ });
84
+ worksheet.commit();
85
+ });
86
+ }
87
+ return Promise.resolve();
88
+ };
89
+ // if there are any uncommitted worksheets, commit them now and wait
90
+ const promises = this._worksheets.map(commitWorksheet);
91
+ if (promises.length) {
92
+ return Promise.all(promises);
93
+ }
94
+ return Promise.resolve();
95
+ }
96
+
97
+ async commit() {
98
+ // commit all worksheets, then add suplimentary files
99
+ await this.promise;
100
+ await this.addMedia();
101
+ await this._commitWorksheets();
102
+ await Promise.all([
103
+ this.addContentTypes(),
104
+ this.addApp(),
105
+ this.addCore(),
106
+ this.addSharedStrings(),
107
+ this.addStyles(),
108
+ this.addWorkbookRels(),
109
+ ]);
110
+ await this.addWorkbook();
111
+ return this._finalize();
112
+ }
113
+
114
+ get nextId() {
115
+ // find the next unique spot to add worksheet
116
+ let i;
117
+ for (i = 1; i < this._worksheets.length; i++) {
118
+ if (!this._worksheets[i]) {
119
+ return i;
120
+ }
121
+ }
122
+ return this._worksheets.length || 1;
123
+ }
124
+
125
+ addImage(image) {
126
+ const id = this.media.length;
127
+ const medium = Object.assign({}, image, {type: 'image', name: `image${id}.${image.extension}`});
128
+ this.media.push(medium);
129
+ return id;
130
+ }
131
+
132
+ getImage(id) {
133
+ return this.media[id];
134
+ }
135
+
136
+ addWorksheet(name, options) {
137
+ // it's possible to add a worksheet with different than default
138
+ // shared string handling
139
+ // in fact, it's even possible to switch it mid-sheet
140
+ options = options || {};
141
+ const useSharedStrings =
142
+ options.useSharedStrings !== undefined ? options.useSharedStrings : this.useSharedStrings;
143
+
144
+ if (options.tabColor) {
145
+ // eslint-disable-next-line no-console
146
+ console.trace('tabColor option has moved to { properties: tabColor: {...} }');
147
+ options.properties = Object.assign(
148
+ {
149
+ tabColor: options.tabColor,
150
+ },
151
+ options.properties
152
+ );
153
+ }
154
+
155
+ const id = this.nextId;
156
+ name = name || `sheet${id}`;
157
+
158
+ const worksheet = new WorksheetWriter({
159
+ id,
160
+ name,
161
+ workbook: this,
162
+ useSharedStrings,
163
+ properties: options.properties,
164
+ state: options.state,
165
+ pageSetup: options.pageSetup,
166
+ views: options.views,
167
+ autoFilter: options.autoFilter,
168
+ headerFooter: options.headerFooter,
169
+ });
170
+
171
+ this._worksheets[id] = worksheet;
172
+ return worksheet;
173
+ }
174
+
175
+ getWorksheet(id) {
176
+ if (id === undefined) {
177
+ return this._worksheets.find(() => true);
178
+ }
179
+ if (typeof id === 'number') {
180
+ return this._worksheets[id];
181
+ }
182
+ if (typeof id === 'string') {
183
+ return this._worksheets.find(worksheet => worksheet && worksheet.name === id);
184
+ }
185
+ return undefined;
186
+ }
187
+
188
+ addStyles() {
189
+ return new Promise(resolve => {
190
+ this.zip.append(this.styles.xml, {name: 'xl/styles.xml'});
191
+ resolve();
192
+ });
193
+ }
194
+
195
+ addThemes() {
196
+ return new Promise(resolve => {
197
+ this.zip.append(theme1Xml, {name: 'xl/theme/theme1.xml'});
198
+ resolve();
199
+ });
200
+ }
201
+
202
+ addOfficeRels() {
203
+ return new Promise(resolve => {
204
+ const xform = new RelationshipsXform();
205
+ const xml = xform.toXml([
206
+ {Id: 'rId1', Type: RelType.OfficeDocument, Target: 'xl/workbook.xml'},
207
+ {Id: 'rId2', Type: RelType.CoreProperties, Target: 'docProps/core.xml'},
208
+ {Id: 'rId3', Type: RelType.ExtenderProperties, Target: 'docProps/app.xml'},
209
+ ]);
210
+ this.zip.append(xml, {name: '/_rels/.rels'});
211
+ resolve();
212
+ });
213
+ }
214
+
215
+ addContentTypes() {
216
+ return new Promise(resolve => {
217
+ const model = {
218
+ worksheets: this._worksheets.filter(Boolean),
219
+ sharedStrings: this.sharedStrings,
220
+ commentRefs: this.commentRefs,
221
+ media: this.media,
222
+ };
223
+ const xform = new ContentTypesXform();
224
+ const xml = xform.toXml(model);
225
+ this.zip.append(xml, {name: '[Content_Types].xml'});
226
+ resolve();
227
+ });
228
+ }
229
+
230
+ addMedia() {
231
+ return Promise.all(
232
+ this.media.map(medium => {
233
+ if (medium.type === 'image') {
234
+ const filename = `xl/media/${medium.name}`;
235
+ if (medium.filename) {
236
+ return this.zip.file(medium.filename, {name: filename});
237
+ }
238
+ if (medium.buffer) {
239
+ return this.zip.append(medium.buffer, {name: filename});
240
+ }
241
+ if (medium.base64) {
242
+ const dataimg64 = medium.base64;
243
+ const content = dataimg64.substring(dataimg64.indexOf(',') + 1);
244
+ return this.zip.append(content, {name: filename, base64: true});
245
+ }
246
+ }
247
+ throw new Error('Unsupported media');
248
+ })
249
+ );
250
+ }
251
+
252
+ addApp() {
253
+ return new Promise(resolve => {
254
+ const model = {
255
+ worksheets: this._worksheets.filter(Boolean),
256
+ };
257
+ const xform = new AppXform();
258
+ const xml = xform.toXml(model);
259
+ this.zip.append(xml, {name: 'docProps/app.xml'});
260
+ resolve();
261
+ });
262
+ }
263
+
264
+ addCore() {
265
+ return new Promise(resolve => {
266
+ const coreXform = new CoreXform();
267
+ const xml = coreXform.toXml(this);
268
+ this.zip.append(xml, {name: 'docProps/core.xml'});
269
+ resolve();
270
+ });
271
+ }
272
+
273
+ addSharedStrings() {
274
+ if (this.sharedStrings.count) {
275
+ return new Promise(resolve => {
276
+ const sharedStringsXform = new SharedStringsXform();
277
+ const xml = sharedStringsXform.toXml(this.sharedStrings);
278
+ this.zip.append(xml, {name: '/xl/sharedStrings.xml'});
279
+ resolve();
280
+ });
281
+ }
282
+ return Promise.resolve();
283
+ }
284
+
285
+ addWorkbookRels() {
286
+ let count = 1;
287
+ const relationships = [
288
+ {Id: `rId${count++}`, Type: RelType.Styles, Target: 'styles.xml'},
289
+ {Id: `rId${count++}`, Type: RelType.Theme, Target: 'theme/theme1.xml'},
290
+ ];
291
+ if (this.sharedStrings.count) {
292
+ relationships.push({
293
+ Id: `rId${count++}`,
294
+ Type: RelType.SharedStrings,
295
+ Target: 'sharedStrings.xml',
296
+ });
297
+ }
298
+ this._worksheets.forEach(worksheet => {
299
+ if (worksheet) {
300
+ worksheet.rId = `rId${count++}`;
301
+ relationships.push({
302
+ Id: worksheet.rId,
303
+ Type: RelType.Worksheet,
304
+ Target: `worksheets/sheet${worksheet.id}.xml`,
305
+ });
306
+ }
307
+ });
308
+ return new Promise(resolve => {
309
+ const xform = new RelationshipsXform();
310
+ const xml = xform.toXml(relationships);
311
+ this.zip.append(xml, {name: '/xl/_rels/workbook.xml.rels'});
312
+ resolve();
313
+ });
314
+ }
315
+
316
+ addWorkbook() {
317
+ const {zip} = this;
318
+ const model = {
319
+ worksheets: this._worksheets.filter(Boolean),
320
+ definedNames: this._definedNames.model,
321
+ views: this.views,
322
+ properties: {},
323
+ calcProperties: {},
324
+ };
325
+
326
+ return new Promise(resolve => {
327
+ const xform = new WorkbookXform();
328
+ xform.prepare(model);
329
+ zip.append(xform.toXml(model), {name: '/xl/workbook.xml'});
330
+ resolve();
331
+ });
332
+ }
333
+
334
+ _finalize() {
335
+ return new Promise((resolve, reject) => {
336
+ this.stream.on('error', reject);
337
+ this.stream.on('finish', () => {
338
+ resolve(this);
339
+ });
340
+ this.zip.on('error', reject);
341
+
342
+ this.zip.finalize();
343
+ });
344
+ }
345
+ }
346
+
347
+ module.exports = WorkbookWriter;