exceljs-lts 4.5.1 → 4.5.3
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.
- package/dist/es5/doc/table.js +89 -2
- package/dist/es5/doc/table.js.map +1 -1
- package/dist/es5/doc/worksheet.js +12 -6
- package/dist/es5/doc/worksheet.js.map +1 -1
- package/dist/es5/stream/xlsx/workbook-reader.js +69 -62
- package/dist/es5/stream/xlsx/workbook-reader.js.map +1 -1
- package/dist/es5/xlsx/xform/book/workbook-xform.js +34 -9
- package/dist/es5/xlsx/xform/book/workbook-xform.js.map +1 -1
- package/dist/es5/xlsx/xform/sheet/cf/cf-rule-xform.js +4 -0
- package/dist/es5/xlsx/xform/sheet/cf/cf-rule-xform.js.map +1 -1
- package/dist/es5/xlsx/xform/sheet/cf/conditional-formattings-xform.js +4 -1
- package/dist/es5/xlsx/xform/sheet/cf/conditional-formattings-xform.js.map +1 -1
- package/dist/es5/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +40 -2
- package/dist/es5/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js.map +1 -1
- package/dist/es5/xlsx/xform/style/dxf-ext-xform.js +24 -0
- package/dist/es5/xlsx/xform/style/dxf-ext-xform.js.map +1 -0
- package/dist/es5/xlsx/xform/table/table-xform.js +2 -1
- package/dist/es5/xlsx/xform/table/table-xform.js.map +1 -1
- package/dist/exceljs.bare.js +621 -432
- package/dist/exceljs.bare.js.map +11 -9
- package/dist/exceljs.bare.min.js +8 -8
- package/dist/exceljs.bare.min.js.map +1 -1
- package/dist/exceljs.js +786 -597
- package/dist/exceljs.js.map +11 -9
- package/dist/exceljs.min.js +8 -8
- package/dist/exceljs.min.js.map +1 -1
- package/lib/doc/table.js +89 -2
- package/lib/doc/worksheet.js +15 -6
- package/lib/stream/xlsx/workbook-reader.js +64 -54
- package/lib/xlsx/xform/book/workbook-xform.js +42 -11
- package/lib/xlsx/xform/sheet/cf/cf-rule-xform.js +4 -0
- package/lib/xlsx/xform/sheet/cf/conditional-formattings-xform.js +3 -1
- package/lib/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +42 -1
- package/lib/xlsx/xform/style/dxf-ext-xform.js +19 -0
- package/lib/xlsx/xform/table/table-xform.js +2 -1
- package/package.json +1 -1
package/lib/doc/table.js
CHANGED
|
@@ -142,8 +142,9 @@ class Table {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
get height() {
|
|
145
|
-
// height of the table data
|
|
146
|
-
|
|
145
|
+
// height of the table data. Excel needs at least one data row, so an empty table
|
|
146
|
+
// keeps one blank row on the sheet (it is not added to table.rows)
|
|
147
|
+
return Math.max(this.table.rows.length, 1);
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
get filterHeight() {
|
|
@@ -246,6 +247,15 @@ class Table {
|
|
|
246
247
|
assignStyle(cell, table.columns[j].style);
|
|
247
248
|
});
|
|
248
249
|
});
|
|
250
|
+
if (!table.rows.length) {
|
|
251
|
+
// blank placeholder row so the table keeps a valid ref
|
|
252
|
+
const r = worksheet.getRow(row + count++);
|
|
253
|
+
table.columns.forEach((column, j) => {
|
|
254
|
+
const cell = r.getCell(col + j);
|
|
255
|
+
cell.value = null;
|
|
256
|
+
assignStyle(cell, column.style);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
249
259
|
|
|
250
260
|
if (table.totalsRow) {
|
|
251
261
|
const r = worksheet.getRow(row + count++);
|
|
@@ -289,6 +299,10 @@ class Table {
|
|
|
289
299
|
cell.value = value;
|
|
290
300
|
});
|
|
291
301
|
});
|
|
302
|
+
if (!table.rows.length) {
|
|
303
|
+
// skip the blank placeholder row of an empty table
|
|
304
|
+
count++;
|
|
305
|
+
}
|
|
292
306
|
|
|
293
307
|
if (table.totalsRow) {
|
|
294
308
|
const r = worksheet.getRow(row + count++);
|
|
@@ -315,6 +329,79 @@ class Table {
|
|
|
315
329
|
|
|
316
330
|
set model(value) {
|
|
317
331
|
this.table = value;
|
|
332
|
+
if (this.worksheet && !value.rows) {
|
|
333
|
+
// a table parsed from a file has no rows: read them from the sheet cells
|
|
334
|
+
this.loadRows();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
loadRows() {
|
|
339
|
+
// rebuild ref, column names, rows and totals row formulas of a table parsed from a file
|
|
340
|
+
// from the cells inside its tableRef. This only reads the sheet, it never writes to it.
|
|
341
|
+
const {worksheet, table} = this;
|
|
342
|
+
if (table.headerRow === undefined) {
|
|
343
|
+
table.headerRow = true;
|
|
344
|
+
}
|
|
345
|
+
if (table.totalsRow === undefined) {
|
|
346
|
+
table.totalsRow = false;
|
|
347
|
+
}
|
|
348
|
+
if (!table.style) {
|
|
349
|
+
table.style = {};
|
|
350
|
+
}
|
|
351
|
+
table.columns = table.columns || [];
|
|
352
|
+
const range = colCache.decode(table.tableRef || table.ref);
|
|
353
|
+
const top = range.top || range.row;
|
|
354
|
+
const left = range.left || range.col;
|
|
355
|
+
const bottom = range.bottom || top;
|
|
356
|
+
if (!table.ref) {
|
|
357
|
+
table.ref = colCache.encodeAddress(top, left);
|
|
358
|
+
}
|
|
359
|
+
const cellValue = (r, c) => {
|
|
360
|
+
const cell = worksheet.findCell(r, c);
|
|
361
|
+
return cell ? cell.value : null;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
let first = top;
|
|
365
|
+
let last = bottom;
|
|
366
|
+
if (table.headerRow) {
|
|
367
|
+
table.columns.forEach((column, j) => {
|
|
368
|
+
if (!column.name) {
|
|
369
|
+
const value = cellValue(top, left + j);
|
|
370
|
+
column.name = value === null || value === undefined ? `Column${j + 1}` : `${value}`;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
first++;
|
|
374
|
+
}
|
|
375
|
+
if (table.totalsRow) {
|
|
376
|
+
table.columns.forEach((column, j) => {
|
|
377
|
+
const cell = worksheet.findCell(bottom, left + j);
|
|
378
|
+
if (!cell) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (j === 0) {
|
|
382
|
+
if (column.totalsRowLabel === undefined && typeof cell.value === 'string') {
|
|
383
|
+
column.totalsRowLabel = cell.value;
|
|
384
|
+
}
|
|
385
|
+
} else if (cell.formula) {
|
|
386
|
+
if (column.totalsRowFunction === 'custom' && column.totalsRowFormula === undefined) {
|
|
387
|
+
column.totalsRowFormula = cell.formula;
|
|
388
|
+
}
|
|
389
|
+
if (column.totalsRowResult === undefined) {
|
|
390
|
+
column.totalsRowResult = cell.result;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
last--;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
table.rows = [];
|
|
398
|
+
for (let r = first; r <= last; r++) {
|
|
399
|
+
table.rows.push(table.columns.map((column, j) => cellValue(r, left + j)));
|
|
400
|
+
}
|
|
401
|
+
if (table.rows.length === 1 && table.rows[0].every(v => v === null || v === undefined)) {
|
|
402
|
+
// the blank placeholder row of an empty table
|
|
403
|
+
table.rows = [];
|
|
404
|
+
}
|
|
318
405
|
}
|
|
319
406
|
|
|
320
407
|
// ================================================================
|
package/lib/doc/worksheet.js
CHANGED
|
@@ -660,12 +660,21 @@ class Worksheet {
|
|
|
660
660
|
}
|
|
661
661
|
|
|
662
662
|
_mergeCellsInternal(dimensions, ignoreStyle) {
|
|
663
|
-
// check cells aren't already merged
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
663
|
+
// check cells aren't already merged. Any existing merge that intersects the range has a
|
|
664
|
+
// cell inside it that is either a merged (slave) cell or a master indexed in this._merges,
|
|
665
|
+
// so this is O(cells in range) rather than O(existing merges).
|
|
666
|
+
// Check everything before changing anything so a rejected merge leaves the sheet untouched.
|
|
667
|
+
for (let i = dimensions.top; i <= dimensions.bottom; i++) {
|
|
668
|
+
for (let j = dimensions.left; j <= dimensions.right; j++) {
|
|
669
|
+
const cell = this.findCell(i, j);
|
|
670
|
+
if (
|
|
671
|
+
(cell && cell.type === Enums.ValueType.Merge) ||
|
|
672
|
+
this._merges[colCache.encodeAddress(i, j)]
|
|
673
|
+
) {
|
|
674
|
+
throw new Error('Cannot merge already merged cells');
|
|
675
|
+
}
|
|
667
676
|
}
|
|
668
|
-
}
|
|
677
|
+
}
|
|
669
678
|
|
|
670
679
|
// apply merge
|
|
671
680
|
const master = this.getCell(dimensions.top, dimensions.left);
|
|
@@ -983,7 +992,7 @@ Please leave feedback at https://github.com/exceljs/exceljs/discussions/2575`
|
|
|
983
992
|
this._media = value.media.map(medium => new Image(this, medium));
|
|
984
993
|
this.sheetProtection = value.sheetProtection;
|
|
985
994
|
this.tables = value.tables.reduce((tables, table) => {
|
|
986
|
-
const t = new Table();
|
|
995
|
+
const t = new Table(this);
|
|
987
996
|
t.model = table;
|
|
988
997
|
tables[table.name] = t;
|
|
989
998
|
return tables;
|
|
@@ -85,67 +85,77 @@ class WorkbookReader extends EventEmitter {
|
|
|
85
85
|
// worksheets, deferred for parsing after shared strings reading
|
|
86
86
|
const waitingWorkSheets = [];
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
88
|
+
try {
|
|
89
|
+
for await (const entry of iterateStream(zip)) {
|
|
90
|
+
let match;
|
|
91
|
+
let sheetNo;
|
|
92
|
+
switch (entry.path) {
|
|
93
|
+
case '_rels/.rels':
|
|
94
|
+
break;
|
|
95
|
+
case 'xl/_rels/workbook.xml.rels':
|
|
96
|
+
await this._parseRels(entry);
|
|
97
|
+
break;
|
|
98
|
+
case 'xl/workbook.xml':
|
|
99
|
+
await this._parseWorkbook(entry);
|
|
100
|
+
break;
|
|
101
|
+
case 'xl/sharedStrings.xml':
|
|
102
|
+
yield* this._parseSharedStrings(entry);
|
|
103
|
+
break;
|
|
104
|
+
case 'xl/styles.xml':
|
|
105
|
+
await this._parseStyles(entry);
|
|
106
|
+
break;
|
|
107
|
+
default:
|
|
108
|
+
if (entry.path.match(/xl\/worksheets\/sheet\d+[.]xml/)) {
|
|
109
|
+
match = entry.path.match(/xl\/worksheets\/sheet(\d+)[.]xml/);
|
|
110
|
+
sheetNo = match[1];
|
|
111
|
+
if (this.sharedStrings && this.workbookRels) {
|
|
112
|
+
yield* this._parseWorksheet(iterateStream(entry), sheetNo);
|
|
113
|
+
} else {
|
|
114
|
+
// create temp file for each worksheet
|
|
115
|
+
await new Promise((resolve, reject) => {
|
|
116
|
+
tmp.file((err, path, fd, tempFileCleanupCallback) => {
|
|
117
|
+
if (err) {
|
|
118
|
+
return reject(err);
|
|
119
|
+
}
|
|
120
|
+
waitingWorkSheets.push({sheetNo, path, tempFileCleanupCallback, cleanedUp: false});
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
const tempStream = fs.createWriteStream(path);
|
|
123
|
+
tempStream.on('error', reject);
|
|
124
|
+
entry.pipe(tempStream);
|
|
125
|
+
return tempStream.on('finish', () => {
|
|
126
|
+
return resolve();
|
|
127
|
+
});
|
|
126
128
|
});
|
|
127
129
|
});
|
|
128
|
-
}
|
|
130
|
+
}
|
|
131
|
+
} else if (entry.path.match(/xl\/worksheets\/_rels\/sheet\d+[.]xml.rels/)) {
|
|
132
|
+
match = entry.path.match(/xl\/worksheets\/_rels\/sheet(\d+)[.]xml.rels/);
|
|
133
|
+
sheetNo = match[1];
|
|
134
|
+
yield* this._parseHyperlinks(iterateStream(entry), sheetNo);
|
|
129
135
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
yield* this._parseHyperlinks(iterateStream(entry), sheetNo);
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
entry.autodrain();
|
|
136
139
|
}
|
|
137
|
-
entry.autodrain();
|
|
138
|
-
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
for (const waitingWorkSheet of waitingWorkSheets) {
|
|
142
|
+
let fileStream = fs.createReadStream(waitingWorkSheet.path);
|
|
143
|
+
// TODO: Remove once node v8 is deprecated
|
|
144
|
+
// Detect and upgrade old fileStreams
|
|
145
|
+
if (!fileStream[Symbol.asyncIterator]) {
|
|
146
|
+
fileStream = fileStream.pipe(new PassThrough());
|
|
147
|
+
}
|
|
148
|
+
yield* this._parseWorksheet(fileStream, waitingWorkSheet.sheetNo);
|
|
149
|
+
waitingWorkSheet.tempFileCleanupCallback();
|
|
150
|
+
waitingWorkSheet.cleanedUp = true;
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
for (const waitingWorkSheet of waitingWorkSheets) {
|
|
154
|
+
if (!waitingWorkSheet.cleanedUp) {
|
|
155
|
+
waitingWorkSheet.tempFileCleanupCallback();
|
|
156
|
+
waitingWorkSheet.cleanedUp = true;
|
|
157
|
+
}
|
|
146
158
|
}
|
|
147
|
-
yield* this._parseWorksheet(fileStream, sheetNo);
|
|
148
|
-
tempFileCleanupCallback();
|
|
149
159
|
}
|
|
150
160
|
}
|
|
151
161
|
|
|
@@ -48,15 +48,18 @@ class WorkbookXform extends BaseXform {
|
|
|
48
48
|
let index = 0; // sheets is sparse array - calc index manually
|
|
49
49
|
model.sheets.forEach(sheet => {
|
|
50
50
|
if (sheet.pageSetup && sheet.pageSetup.printArea) {
|
|
51
|
-
sheet
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
// Excel allows only one Print_Area per sheet; multiple areas are comma separated ranges
|
|
52
|
+
const ranges = sheet.pageSetup.printArea
|
|
53
|
+
.split('&&')
|
|
54
|
+
.filter(printArea => printArea)
|
|
55
|
+
.map(printArea => `'${sheet.name}'!${toAbsoluteRange(printArea)}`);
|
|
56
|
+
if (ranges.length) {
|
|
57
|
+
printAreas.push({
|
|
54
58
|
name: '_xlnm.Print_Area',
|
|
55
|
-
ranges
|
|
59
|
+
ranges,
|
|
56
60
|
localSheetId: index,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
if (sheet.pageSetup && (sheet.pageSetup.printTitlesRow || sheet.pageSetup.printTitlesColumn)) {
|
|
@@ -197,10 +200,12 @@ class WorkbookXform extends BaseXform {
|
|
|
197
200
|
if (!worksheet.pageSetup) {
|
|
198
201
|
worksheet.pageSetup = {};
|
|
199
202
|
}
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
const printArea = definedName.ranges.map(fromDefinedNameRange).join('&&');
|
|
204
|
+
if (printArea) {
|
|
205
|
+
worksheet.pageSetup.printArea = worksheet.pageSetup.printArea
|
|
206
|
+
? `${worksheet.pageSetup.printArea}&&${printArea}`
|
|
207
|
+
: printArea;
|
|
208
|
+
}
|
|
204
209
|
}
|
|
205
210
|
} else if (definedName.name === '_xlnm.Print_Titles') {
|
|
206
211
|
worksheet = worksheets[definedName.localSheetId];
|
|
@@ -242,6 +247,32 @@ class WorkbookXform extends BaseXform {
|
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
249
|
|
|
250
|
+
// convert a print area such as A1:B2, A:B, 1:3 or C3 into an absolute reference
|
|
251
|
+
// ($A$1:$B$2, $A:$B, $1:$3, $C$3)
|
|
252
|
+
function toAbsoluteRange(range) {
|
|
253
|
+
return range
|
|
254
|
+
.split(':')
|
|
255
|
+
.map(part => {
|
|
256
|
+
const match = part.trim().match(/^\$?([A-Za-z]*)\$?(\d*)$/);
|
|
257
|
+
if (!match) {
|
|
258
|
+
return part;
|
|
259
|
+
}
|
|
260
|
+
const [, col, row] = match;
|
|
261
|
+
return `${col ? `$${col.toUpperCase()}` : ''}${row ? `$${row}` : ''}`;
|
|
262
|
+
})
|
|
263
|
+
.join(':');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// convert a defined name range such as 'Sheet'!$A$1:$B$2 or Sheet!$A:$B into a print area
|
|
267
|
+
// (A1:B2, A:B)
|
|
268
|
+
function fromDefinedNameRange(range) {
|
|
269
|
+
const reference = range.match(/^(?:(?:'(?:[^']|'')*'|[^'!]*)!)?(.*)$/)[1];
|
|
270
|
+
if (/^\$?[A-Za-z]+\$?\d+:\$?[A-Za-z]+\$?\d+$/.test(reference)) {
|
|
271
|
+
return colCache.decodeEx(reference).dimensions;
|
|
272
|
+
}
|
|
273
|
+
return reference.replace(/\$/g, '');
|
|
274
|
+
}
|
|
275
|
+
|
|
245
276
|
WorkbookXform.WORKBOOK_ATTRIBUTES = {
|
|
246
277
|
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
247
278
|
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const BaseXform = require('../../base-xform');
|
|
2
2
|
|
|
3
3
|
const ConditionalFormattingXform = require('./conditional-formatting-xform');
|
|
4
|
+
const CfRuleXform = require('./cf-rule-xform');
|
|
4
5
|
|
|
5
6
|
class ConditionalFormattingsXform extends BaseXform {
|
|
6
7
|
constructor() {
|
|
@@ -29,7 +30,8 @@ class ConditionalFormattingsXform extends BaseXform {
|
|
|
29
30
|
rule.priority = nextPriority++;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
// ext (x14) rules write their style inline as <x14:dxf>, not as a dxfId
|
|
34
|
+
if (rule.style && CfRuleXform.isPrimitive(rule)) {
|
|
33
35
|
rule.dxfId = options.styles.addDxfStyle(rule.style);
|
|
34
36
|
}
|
|
35
37
|
});
|
|
@@ -4,6 +4,8 @@ const CompositeXform = require('../../composite-xform');
|
|
|
4
4
|
|
|
5
5
|
const DatabarExtXform = require('./databar-ext-xform');
|
|
6
6
|
const IconSetExtXform = require('./icon-set-ext-xform');
|
|
7
|
+
const FExtXform = require('./f-ext-xform');
|
|
8
|
+
const DxfExtXform = require('../../style/dxf-ext-xform');
|
|
7
9
|
|
|
8
10
|
const extIcons = {
|
|
9
11
|
'3Triangles': true,
|
|
@@ -18,6 +20,8 @@ class CfRuleExtXform extends CompositeXform {
|
|
|
18
20
|
this.map = {
|
|
19
21
|
'x14:dataBar': (this.databarXform = new DatabarExtXform()),
|
|
20
22
|
'x14:iconSet': (this.iconSetXform = new IconSetExtXform()),
|
|
23
|
+
'xm:f': (this.fXform = new FExtXform()),
|
|
24
|
+
'x14:dxf': (this.dxfXform = new DxfExtXform()),
|
|
21
25
|
};
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -35,6 +39,11 @@ class CfRuleExtXform extends CompositeXform {
|
|
|
35
39
|
return true;
|
|
36
40
|
}
|
|
37
41
|
}
|
|
42
|
+
if (rule.type === 'expression') {
|
|
43
|
+
// expression rules read from x14:cfRule (e.g. formulas that reference other sheets)
|
|
44
|
+
// keep their x14Id and are written back to the extLst
|
|
45
|
+
return !!rule.x14Id;
|
|
46
|
+
}
|
|
38
47
|
return false;
|
|
39
48
|
}
|
|
40
49
|
|
|
@@ -56,6 +65,9 @@ class CfRuleExtXform extends CompositeXform {
|
|
|
56
65
|
case 'iconSet':
|
|
57
66
|
this.renderIconSet(xmlStream, model);
|
|
58
67
|
break;
|
|
68
|
+
case 'expression':
|
|
69
|
+
this.renderExpression(xmlStream, model);
|
|
70
|
+
break;
|
|
59
71
|
}
|
|
60
72
|
}
|
|
61
73
|
|
|
@@ -82,6 +94,23 @@ class CfRuleExtXform extends CompositeXform {
|
|
|
82
94
|
xmlStream.closeNode();
|
|
83
95
|
}
|
|
84
96
|
|
|
97
|
+
renderExpression(xmlStream, model) {
|
|
98
|
+
xmlStream.openNode(this.tag, {
|
|
99
|
+
type: 'expression',
|
|
100
|
+
priority: model.priority,
|
|
101
|
+
id: model.x14Id,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (model.formulae && model.formulae[0]) {
|
|
105
|
+
this.fXform.render(xmlStream, model.formulae[0]);
|
|
106
|
+
}
|
|
107
|
+
if (model.style) {
|
|
108
|
+
this.dxfXform.render(xmlStream, model.style);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
xmlStream.closeNode();
|
|
112
|
+
}
|
|
113
|
+
|
|
85
114
|
createNewModel({attributes}) {
|
|
86
115
|
return {
|
|
87
116
|
type: attributes.type,
|
|
@@ -91,7 +120,19 @@ class CfRuleExtXform extends CompositeXform {
|
|
|
91
120
|
}
|
|
92
121
|
|
|
93
122
|
onParserClose(name, parser) {
|
|
94
|
-
|
|
123
|
+
switch (name) {
|
|
124
|
+
case 'xm:f':
|
|
125
|
+
this.model.formulae = this.model.formulae || [];
|
|
126
|
+
this.model.formulae.push(parser.model);
|
|
127
|
+
break;
|
|
128
|
+
case 'x14:dxf':
|
|
129
|
+
// same shape as the style of a regular cfRule (reconciled from its dxfId)
|
|
130
|
+
this.model.style = parser.model;
|
|
131
|
+
break;
|
|
132
|
+
default:
|
|
133
|
+
Object.assign(this.model, parser.model);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
95
136
|
}
|
|
96
137
|
}
|
|
97
138
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const DxfXform = require('./dxf-xform');
|
|
2
|
+
|
|
3
|
+
// <x14:dxf> holds a differential format inline in an x14:cfRule (extLst).
|
|
4
|
+
// It has the same children as <dxf> in styles.xml.
|
|
5
|
+
class DxfExtXform extends DxfXform {
|
|
6
|
+
get tag() {
|
|
7
|
+
return 'x14:dxf';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
render(xmlStream, model) {
|
|
11
|
+
// a dxf read from a file holds numFmt as a NumFmtXform model ({id, formatCode})
|
|
12
|
+
if (model.numFmt && typeof model.numFmt === 'object') {
|
|
13
|
+
model = {...model, numFmtId: model.numFmt.id, numFmt: model.numFmt.formatCode};
|
|
14
|
+
}
|
|
15
|
+
super.render(xmlStream, model);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = DxfExtXform;
|
|
@@ -66,7 +66,8 @@ class TableXform extends BaseXform {
|
|
|
66
66
|
displayName: attributes.displayName || attributes.name,
|
|
67
67
|
tableRef: attributes.ref,
|
|
68
68
|
totalsRow: attributes.totalsRowCount === '1',
|
|
69
|
-
|
|
69
|
+
// headerRowCount defaults to 1 and Excel usually omits it
|
|
70
|
+
headerRow: attributes.headerRowCount !== '0',
|
|
70
71
|
};
|
|
71
72
|
break;
|
|
72
73
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exceljs-lts",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.3",
|
|
4
4
|
"description": "Maintained fork of exceljs — drop-in replacement, fixes Excel \"We found a problem with some content\" repair prompts, the TypeError crash on rich-text shared strings, and lost formula results (0, false, ''). Read and write xlsx and csv files.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|