exceljs-lts 4.5.4 → 4.5.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.
- package/README.md +28 -11
- package/README_zh.md +18 -10
- package/dist/es5/doc/cell.js +37 -12
- package/dist/es5/doc/cell.js.map +1 -1
- package/dist/es5/doc/data-validations.js +94 -1
- package/dist/es5/doc/data-validations.js.map +1 -1
- package/dist/es5/stream/xlsx/workbook-reader.js +2 -1
- package/dist/es5/stream/xlsx/workbook-reader.js.map +1 -1
- package/dist/es5/stream/xlsx/workbook-writer.js +21 -1
- package/dist/es5/stream/xlsx/workbook-writer.js.map +1 -1
- package/dist/es5/stream/xlsx/worksheet-reader.js +1 -1
- package/dist/es5/stream/xlsx/worksheet-reader.js.map +1 -1
- package/dist/es5/stream/xlsx/worksheet-writer.js +87 -1
- package/dist/es5/stream/xlsx/worksheet-writer.js.map +1 -1
- package/dist/es5/xlsx/rel-type.js +1 -0
- package/dist/es5/xlsx/rel-type.js.map +1 -1
- package/dist/es5/xlsx/xform/sheet/data-validations-xform.js +14 -3
- package/dist/es5/xlsx/xform/sheet/data-validations-xform.js.map +1 -1
- package/dist/exceljs.bare.js +148 -18
- package/dist/exceljs.bare.js.map +5 -5
- package/dist/exceljs.bare.min.js +1 -1
- package/dist/exceljs.bare.min.js.map +1 -1
- package/dist/exceljs.js +148 -18
- package/dist/exceljs.js.map +5 -5
- package/dist/exceljs.min.js +4 -4
- package/dist/exceljs.min.js.map +1 -1
- package/index.d.ts +36 -3
- package/lib/doc/cell.js +37 -12
- package/lib/doc/data-validations.js +83 -1
- package/lib/stream/xlsx/workbook-reader.js +6 -1
- package/lib/stream/xlsx/workbook-writer.js +19 -0
- package/lib/stream/xlsx/worksheet-reader.js +1 -1
- package/lib/stream/xlsx/worksheet-writer.js +71 -1
- package/lib/xlsx/rel-type.js +1 -0
- package/lib/xlsx/xform/sheet/data-validations-xform.js +14 -3
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Node's Buffer type when @types/node is loaded, otherwise never.
|
|
3
|
+
* Read through `typeof globalThis` so these typings do not require @types/node.
|
|
4
|
+
* Newer @types/node declare `var Buffer`; older ones (class Buffer) expose it as `global.Buffer`.
|
|
5
|
+
*/
|
|
6
|
+
type NodeBufferType = typeof globalThis extends { Buffer: { alloc(size: number): infer B } }
|
|
7
|
+
? B
|
|
8
|
+
: typeof globalThis extends { global: { Buffer: { alloc(size: number): infer B } } }
|
|
9
|
+
? B
|
|
10
|
+
: never;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Binary data returned by writeBuffer(): a Node Buffer when @types/node is loaded
|
|
14
|
+
* (which is what Node returns at runtime), otherwise ArrayBuffer.
|
|
15
|
+
*/
|
|
16
|
+
declare type Buffer = [NodeBufferType] extends [never] ? ArrayBuffer : NodeBufferType;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Binary data accepted by xlsx.load().
|
|
20
|
+
*/
|
|
21
|
+
type BufferInput = Buffer | ArrayBuffer | Uint8Array;
|
|
2
22
|
|
|
3
23
|
export declare enum RelationshipType {
|
|
4
24
|
None = 0,
|
|
@@ -830,10 +850,14 @@ export interface PageSetup {
|
|
|
830
850
|
export interface HeaderFooter {
|
|
831
851
|
/**
|
|
832
852
|
* Set the value of differentFirst as true, which indicates that headers/footers for first page are different from the other pages, `false` by default
|
|
853
|
+
*
|
|
854
|
+
* Must be `true` for `firstHeader`/`firstFooter` to take effect.
|
|
833
855
|
*/
|
|
834
856
|
differentFirst: boolean,
|
|
835
857
|
/**
|
|
836
858
|
* Set the value of differentOddEven as true, which indicates that headers/footers for odd and even pages are different, `false` by default
|
|
859
|
+
*
|
|
860
|
+
* Must be `true` for `evenHeader`/`evenFooter` to take effect.
|
|
837
861
|
*/
|
|
838
862
|
differentOddEven: boolean,
|
|
839
863
|
/**
|
|
@@ -846,18 +870,26 @@ export interface HeaderFooter {
|
|
|
846
870
|
oddFooter: string,
|
|
847
871
|
/**
|
|
848
872
|
* Set header string for even pages, could format the string and `null` by default
|
|
873
|
+
*
|
|
874
|
+
* Only used when `differentOddEven` is `true`; otherwise Excel ignores it.
|
|
849
875
|
*/
|
|
850
876
|
evenHeader: string,
|
|
851
877
|
/**
|
|
852
878
|
* Set footer string for even pages, could format the string and `null` by default
|
|
879
|
+
*
|
|
880
|
+
* Only used when `differentOddEven` is `true`; otherwise Excel ignores it.
|
|
853
881
|
*/
|
|
854
882
|
evenFooter: string,
|
|
855
883
|
/**
|
|
856
884
|
* Set header string for the first page, could format the string and `null` by default
|
|
885
|
+
*
|
|
886
|
+
* Only used when `differentFirst` is `true`; otherwise Excel ignores it.
|
|
857
887
|
*/
|
|
858
888
|
firstHeader: string,
|
|
859
889
|
/**
|
|
860
890
|
* Set footer string for the first page, could format the string and `null` by default
|
|
891
|
+
*
|
|
892
|
+
* Only used when `differentFirst` is `true`; otherwise Excel ignores it.
|
|
861
893
|
*/
|
|
862
894
|
firstFooter: string
|
|
863
895
|
}
|
|
@@ -893,7 +925,7 @@ export interface Image {
|
|
|
893
925
|
extension: 'jpeg' | 'png' | 'gif';
|
|
894
926
|
base64?: string;
|
|
895
927
|
filename?: string;
|
|
896
|
-
buffer?: Buffer;
|
|
928
|
+
buffer?: Buffer | ArrayBuffer;
|
|
897
929
|
}
|
|
898
930
|
export interface IAnchor {
|
|
899
931
|
col: number;
|
|
@@ -1498,7 +1530,7 @@ export interface Xlsx {
|
|
|
1498
1530
|
* load from an array buffer
|
|
1499
1531
|
* @param buffer
|
|
1500
1532
|
*/
|
|
1501
|
-
load(buffer:
|
|
1533
|
+
load(buffer: BufferInput, options?: Partial<XlsxReadOptions>): Promise<Workbook>;
|
|
1502
1534
|
|
|
1503
1535
|
/**
|
|
1504
1536
|
* write to a buffer
|
|
@@ -1993,6 +2025,7 @@ export namespace stream {
|
|
|
1993
2025
|
addThemes(): Promise<void>;
|
|
1994
2026
|
addOfficeRels(): Promise<void>;
|
|
1995
2027
|
addContentTypes(): Promise<void>;
|
|
2028
|
+
addDrawings(): Promise<void>;
|
|
1996
2029
|
addApp(): Promise<void>;
|
|
1997
2030
|
addCore(): Promise<void>;
|
|
1998
2031
|
addSharedStrings(): Promise<void>;
|
package/lib/doc/cell.js
CHANGED
|
@@ -4,6 +4,7 @@ const _ = require('../utils/under-dash');
|
|
|
4
4
|
const Enums = require('./enums');
|
|
5
5
|
const {slideFormula} = require('../utils/shared-formula');
|
|
6
6
|
const Note = require('./note');
|
|
7
|
+
const {copyStyle} = require('../utils/copy-style');
|
|
7
8
|
// Cell requirements
|
|
8
9
|
// Operate inside a worksheet
|
|
9
10
|
// Store and retrieve a value with a range of types: text, number, date, hyperlink, reference, formula, etc.
|
|
@@ -39,7 +40,8 @@ class Cell {
|
|
|
39
40
|
|
|
40
41
|
// help GC by removing cyclic (and other) references
|
|
41
42
|
destroy() {
|
|
42
|
-
delete this.
|
|
43
|
+
delete this._style;
|
|
44
|
+
delete this._styleShared;
|
|
43
45
|
delete this._value;
|
|
44
46
|
delete this._row;
|
|
45
47
|
delete this._column;
|
|
@@ -48,8 +50,31 @@ class Cell {
|
|
|
48
50
|
|
|
49
51
|
// =========================================================================
|
|
50
52
|
// Styles stuff
|
|
53
|
+
|
|
54
|
+
// Cells loaded from a file share one style object per xf. The object is only
|
|
55
|
+
// copied when the style is handed out (cell.style), so code that mutates it
|
|
56
|
+
// in place (cell.style.fill = ...) changes this cell alone.
|
|
57
|
+
get style() {
|
|
58
|
+
if (this._styleShared) {
|
|
59
|
+
this._style = copyStyle(this._style);
|
|
60
|
+
this._styleShared = false;
|
|
61
|
+
}
|
|
62
|
+
return this._style;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
set style(value) {
|
|
66
|
+
this._style = value;
|
|
67
|
+
this._styleShared = false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// use a style object that other cells may share (e.g. a cached xf style)
|
|
71
|
+
_setSharedStyle(style) {
|
|
72
|
+
this._style = style;
|
|
73
|
+
this._styleShared = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
51
76
|
get numFmt() {
|
|
52
|
-
return this.
|
|
77
|
+
return this._style.numFmt;
|
|
53
78
|
}
|
|
54
79
|
|
|
55
80
|
set numFmt(value) {
|
|
@@ -57,7 +82,7 @@ class Cell {
|
|
|
57
82
|
}
|
|
58
83
|
|
|
59
84
|
get font() {
|
|
60
|
-
return this.
|
|
85
|
+
return this._style.font;
|
|
61
86
|
}
|
|
62
87
|
|
|
63
88
|
set font(value) {
|
|
@@ -65,7 +90,7 @@ class Cell {
|
|
|
65
90
|
}
|
|
66
91
|
|
|
67
92
|
get alignment() {
|
|
68
|
-
return this.
|
|
93
|
+
return this._style.alignment;
|
|
69
94
|
}
|
|
70
95
|
|
|
71
96
|
set alignment(value) {
|
|
@@ -73,7 +98,7 @@ class Cell {
|
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
get border() {
|
|
76
|
-
return this.
|
|
101
|
+
return this._style.border;
|
|
77
102
|
}
|
|
78
103
|
|
|
79
104
|
set border(value) {
|
|
@@ -81,7 +106,7 @@ class Cell {
|
|
|
81
106
|
}
|
|
82
107
|
|
|
83
108
|
get fill() {
|
|
84
|
-
return this.
|
|
109
|
+
return this._style.fill;
|
|
85
110
|
}
|
|
86
111
|
|
|
87
112
|
set fill(value) {
|
|
@@ -89,7 +114,7 @@ class Cell {
|
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
get protection() {
|
|
92
|
-
return this.
|
|
117
|
+
return this._style.protection;
|
|
93
118
|
}
|
|
94
119
|
|
|
95
120
|
set protection(value) {
|
|
@@ -99,7 +124,7 @@ class Cell {
|
|
|
99
124
|
_setStyle(name, value) {
|
|
100
125
|
// style objects can be shared (e.g. by every cell loaded with the same xf),
|
|
101
126
|
// so never mutate one in place; give this cell a new object instead
|
|
102
|
-
const oldStyle = this.
|
|
127
|
+
const oldStyle = this._style;
|
|
103
128
|
const style = {...oldStyle, [name]: value};
|
|
104
129
|
this.style = style;
|
|
105
130
|
|
|
@@ -112,12 +137,12 @@ class Cell {
|
|
|
112
137
|
(Array.isArray(merges)
|
|
113
138
|
? merges.find(merge => merge.top === master.row && merge.left === master.col)
|
|
114
139
|
: merges[master.address]);
|
|
115
|
-
if (range && (master === this || master.
|
|
140
|
+
if (range && (master === this || master._style === oldStyle)) {
|
|
116
141
|
for (let r = range.top; r <= range.bottom; r++) {
|
|
117
142
|
const row = this.worksheet.findRow(r);
|
|
118
143
|
for (let c = range.left; row && c <= range.right; c++) {
|
|
119
144
|
const cell = row.findCell(c);
|
|
120
|
-
if (cell && cell.
|
|
145
|
+
if (cell && cell._style === oldStyle) {
|
|
121
146
|
cell.style = style;
|
|
122
147
|
}
|
|
123
148
|
}
|
|
@@ -357,7 +382,7 @@ class Cell {
|
|
|
357
382
|
|
|
358
383
|
get model() {
|
|
359
384
|
const {model} = this._value;
|
|
360
|
-
model.style = this.
|
|
385
|
+
model.style = this._style;
|
|
361
386
|
if (this._comment) {
|
|
362
387
|
model.comment = this._comment.model;
|
|
363
388
|
}
|
|
@@ -378,7 +403,7 @@ class Cell {
|
|
|
378
403
|
}
|
|
379
404
|
|
|
380
405
|
if (value.style) {
|
|
381
|
-
this.
|
|
406
|
+
this._setSharedStyle(value.style);
|
|
382
407
|
} else {
|
|
383
408
|
this.style = {};
|
|
384
409
|
}
|
|
@@ -1,19 +1,101 @@
|
|
|
1
|
+
const colCache = require('../utils/col-cache');
|
|
2
|
+
|
|
3
|
+
function isRange(address) {
|
|
4
|
+
return typeof address === 'string' && address.includes(':');
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
class DataValidations {
|
|
2
8
|
constructor(model) {
|
|
3
9
|
this.model = model || {};
|
|
10
|
+
// Range-keyed entries (e.g. 'A2:A1048576', kept intact when a file is loaded) are tracked
|
|
11
|
+
// separately so per-cell add/find stay O(1) when there are none (exceljs#1842).
|
|
12
|
+
this._ranges = new Map();
|
|
13
|
+
Object.keys(this.model).forEach(key => {
|
|
14
|
+
if (isRange(key)) {
|
|
15
|
+
this._addRange(key);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
4
18
|
}
|
|
5
19
|
|
|
6
20
|
add(address, validation) {
|
|
21
|
+
if (isRange(address)) {
|
|
22
|
+
this._addRange(address);
|
|
23
|
+
} else {
|
|
24
|
+
this._splitRangeAt(address);
|
|
25
|
+
}
|
|
7
26
|
return (this.model[address] = validation);
|
|
8
27
|
}
|
|
9
28
|
|
|
10
29
|
find(address) {
|
|
11
|
-
|
|
30
|
+
const validation = this.model[address];
|
|
31
|
+
if (validation !== undefined || isRange(address) || !this._ranges.size) {
|
|
32
|
+
return validation;
|
|
33
|
+
}
|
|
34
|
+
// fall back to range-keyed entries that contain the address
|
|
35
|
+
const key = this._findRangeKey(address);
|
|
36
|
+
return key ? this.model[key] : undefined;
|
|
12
37
|
}
|
|
13
38
|
|
|
14
39
|
remove(address) {
|
|
40
|
+
if (isRange(address)) {
|
|
41
|
+
this._ranges.delete(address);
|
|
42
|
+
} else {
|
|
43
|
+
this._splitRangeAt(address);
|
|
44
|
+
}
|
|
15
45
|
this.model[address] = undefined;
|
|
16
46
|
}
|
|
47
|
+
|
|
48
|
+
_addRange(key) {
|
|
49
|
+
if (!this._ranges.has(key)) {
|
|
50
|
+
this._ranges.set(key, colCache.decodeEx(key));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_findRangeKey(address) {
|
|
55
|
+
if (!this._ranges.size) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const {row, col} = colCache.decodeAddress(address);
|
|
59
|
+
for (const [key, {top, left, bottom, right}] of this._ranges) {
|
|
60
|
+
if (this.model[key] && row >= top && row <= bottom && col >= left && col <= right) {
|
|
61
|
+
return key;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// If a single cell lies inside a range-keyed entry, replace that entry with up to four
|
|
68
|
+
// rectangles covering the rest of the range, so the cell can get its own validation
|
|
69
|
+
// (or none) without overlapping sqrefs.
|
|
70
|
+
_splitRangeAt(address) {
|
|
71
|
+
const key = this._findRangeKey(address);
|
|
72
|
+
if (!key) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const validation = this.model[key];
|
|
76
|
+
const {row, col} = colCache.decodeAddress(address);
|
|
77
|
+
const {top, left, bottom, right} = this._ranges.get(key);
|
|
78
|
+
delete this.model[key];
|
|
79
|
+
this._ranges.delete(key);
|
|
80
|
+
|
|
81
|
+
const addRect = (t, l, b, r) => {
|
|
82
|
+
if (t > b || l > r) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const tl = colCache.encodeAddress(t, l);
|
|
86
|
+
if (t === b && l === r) {
|
|
87
|
+
this.model[tl] = validation;
|
|
88
|
+
} else {
|
|
89
|
+
const rangeKey = `${tl}:${colCache.encodeAddress(b, r)}`;
|
|
90
|
+
this.model[rangeKey] = validation;
|
|
91
|
+
this._addRange(rangeKey);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
addRect(top, left, row - 1, right); // rows above
|
|
95
|
+
addRect(row + 1, left, bottom, right); // rows below
|
|
96
|
+
addRect(row, left, row, col - 1); // left of the cell
|
|
97
|
+
addRect(row, col + 1, row, right); // right of the cell
|
|
98
|
+
}
|
|
17
99
|
}
|
|
18
100
|
|
|
19
101
|
module.exports = DataValidations;
|
|
@@ -108,7 +108,11 @@ class WorkbookReader extends EventEmitter {
|
|
|
108
108
|
if (entry.path.match(/xl\/worksheets\/sheet\d+[.]xml/)) {
|
|
109
109
|
match = entry.path.match(/xl\/worksheets\/sheet(\d+)[.]xml/);
|
|
110
110
|
sheetNo = match[1];
|
|
111
|
-
if (
|
|
111
|
+
if (
|
|
112
|
+
this.sharedStrings &&
|
|
113
|
+
this.workbookRels &&
|
|
114
|
+
(this.options.styles !== 'cache' || this.stylesParsed)
|
|
115
|
+
) {
|
|
112
116
|
yield* this._parseWorksheet(iterateStream(entry), sheetNo);
|
|
113
117
|
} else {
|
|
114
118
|
// create temp file for each worksheet
|
|
@@ -316,6 +320,7 @@ class WorkbookReader extends EventEmitter {
|
|
|
316
320
|
if (this.options.styles === 'cache') {
|
|
317
321
|
this.styles = new StyleManager();
|
|
318
322
|
await this.styles.parseStream(iterateStream(entry));
|
|
323
|
+
this.stylesParsed = true;
|
|
319
324
|
}
|
|
320
325
|
}
|
|
321
326
|
|
|
@@ -14,6 +14,7 @@ const ContentTypesXform = require('../../xlsx/xform/core/content-types-xform');
|
|
|
14
14
|
const AppXform = require('../../xlsx/xform/core/app-xform');
|
|
15
15
|
const WorkbookXform = require('../../xlsx/xform/book/workbook-xform');
|
|
16
16
|
const SharedStringsXform = require('../../xlsx/xform/strings/shared-strings-xform');
|
|
17
|
+
const DrawingXform = require('../../xlsx/xform/drawing/drawing-xform');
|
|
17
18
|
|
|
18
19
|
const WorksheetWriter = require('./worksheet-writer');
|
|
19
20
|
|
|
@@ -45,6 +46,7 @@ class WorkbookWriter {
|
|
|
45
46
|
this.zipOptions = options.zip;
|
|
46
47
|
|
|
47
48
|
this.media = [];
|
|
49
|
+
this.drawings = [];
|
|
48
50
|
this.commentRefs = [];
|
|
49
51
|
|
|
50
52
|
this.zip = Archiver('zip', this.zipOptions);
|
|
@@ -99,6 +101,7 @@ class WorkbookWriter {
|
|
|
99
101
|
await this.promise;
|
|
100
102
|
await this.addMedia();
|
|
101
103
|
await this._commitWorksheets();
|
|
104
|
+
await this.addDrawings();
|
|
102
105
|
await Promise.all([
|
|
103
106
|
this.addContentTypes(),
|
|
104
107
|
this.addApp(),
|
|
@@ -219,6 +222,7 @@ class WorkbookWriter {
|
|
|
219
222
|
sharedStrings: this.sharedStrings,
|
|
220
223
|
commentRefs: this.commentRefs,
|
|
221
224
|
media: this.media,
|
|
225
|
+
drawings: this.drawings,
|
|
222
226
|
};
|
|
223
227
|
const xform = new ContentTypesXform();
|
|
224
228
|
const xml = xform.toXml(model);
|
|
@@ -249,6 +253,21 @@ class WorkbookWriter {
|
|
|
249
253
|
);
|
|
250
254
|
}
|
|
251
255
|
|
|
256
|
+
addDrawings() {
|
|
257
|
+
return new Promise(resolve => {
|
|
258
|
+
const drawingXform = new DrawingXform();
|
|
259
|
+
const relsXform = new RelationshipsXform();
|
|
260
|
+
this.drawings.forEach(drawing => {
|
|
261
|
+
drawingXform.prepare(drawing, {});
|
|
262
|
+
this.zip.append(drawingXform.toXml(drawing), {name: `xl/drawings/${drawing.name}.xml`});
|
|
263
|
+
this.zip.append(relsXform.toXml(drawing.rels), {
|
|
264
|
+
name: `xl/drawings/_rels/${drawing.name}.xml.rels`,
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
resolve();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
252
271
|
addApp() {
|
|
253
272
|
return new Promise(resolve => {
|
|
254
273
|
const model = {
|
|
@@ -13,6 +13,7 @@ const Column = require('../../doc/column');
|
|
|
13
13
|
const SheetRelsWriter = require('./sheet-rels-writer');
|
|
14
14
|
const SheetCommentsWriter = require('./sheet-comments-writer');
|
|
15
15
|
const DataValidations = require('../../doc/data-validations');
|
|
16
|
+
const Image = require('../../doc/image');
|
|
16
17
|
|
|
17
18
|
const xmlBuffer = new StringBuf();
|
|
18
19
|
|
|
@@ -246,6 +247,7 @@ class WorksheetWriter {
|
|
|
246
247
|
this._writePageSetup();
|
|
247
248
|
this._writeHeaderFooter();
|
|
248
249
|
this._writeRowBreaks();
|
|
250
|
+
this._writeDrawing(); // must be after rowBreaks and before legacyDrawing
|
|
249
251
|
|
|
250
252
|
// Legacy Data tag for comments
|
|
251
253
|
this._writeLegacyData();
|
|
@@ -472,10 +474,34 @@ class WorksheetWriter {
|
|
|
472
474
|
|
|
473
475
|
// =========================================================================
|
|
474
476
|
|
|
475
|
-
|
|
477
|
+
_validateImageId(imageId) {
|
|
476
478
|
if (imageId === undefined || imageId === null || !this._workbook.getImage(imageId)) {
|
|
477
479
|
throw new Error(`Image with id ${imageId} not found. Add it with workbook.addImage() first`);
|
|
478
480
|
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
addImage(imageId, range) {
|
|
484
|
+
if (this.committed) {
|
|
485
|
+
throw new Error('Cannot add an image to a committed worksheet');
|
|
486
|
+
}
|
|
487
|
+
this._validateImageId(imageId);
|
|
488
|
+
Image.validateRange(range);
|
|
489
|
+
// Anchors look up column widths and row heights to convert fractional positions.
|
|
490
|
+
// Only look at what already exists: getRow() would create (and later write) empty rows.
|
|
491
|
+
const lookup = {
|
|
492
|
+
properties: this.properties,
|
|
493
|
+
getColumn: c => this._columns && this._columns[c - 1],
|
|
494
|
+
getRow: r => this.findRow(r),
|
|
495
|
+
};
|
|
496
|
+
this._media.push(new Image(lookup, {type: 'image', imageId, range}));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
getImages() {
|
|
500
|
+
return this._media.filter(m => m.type === 'image');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
addBackgroundImage(imageId) {
|
|
504
|
+
this._validateImageId(imageId);
|
|
479
505
|
this._background = {
|
|
480
506
|
imageId,
|
|
481
507
|
};
|
|
@@ -699,6 +725,50 @@ class WorksheetWriter {
|
|
|
699
725
|
}
|
|
700
726
|
}
|
|
701
727
|
|
|
728
|
+
_writeDrawing() {
|
|
729
|
+
const images = this.getImages();
|
|
730
|
+
if (!images.length) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
const name = `drawing${this.id}`;
|
|
734
|
+
const rId = this._sheetRelsWriter.addRelationship({
|
|
735
|
+
Target: `../drawings/${name}.xml`,
|
|
736
|
+
Type: RelType.Drawing,
|
|
737
|
+
});
|
|
738
|
+
const drawing = {rId, name, anchors: [], rels: []};
|
|
739
|
+
const nextRid = () => `rId${drawing.rels.length + 1}`;
|
|
740
|
+
const imageRIds = {};
|
|
741
|
+
images.forEach(image => {
|
|
742
|
+
const {imageId, range, hyperlinks} = image.model;
|
|
743
|
+
let imageRId = imageRIds[imageId];
|
|
744
|
+
if (!imageRId) {
|
|
745
|
+
imageRId = imageRIds[imageId] = nextRid();
|
|
746
|
+
drawing.rels.push({
|
|
747
|
+
Id: imageRId,
|
|
748
|
+
Type: RelType.Image,
|
|
749
|
+
Target: `../media/${this._workbook.getImage(imageId).name}`,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
const anchor = {picture: {rId: imageRId}, range};
|
|
753
|
+
if (hyperlinks && hyperlinks.hyperlink) {
|
|
754
|
+
const hyperlinkRId = nextRid();
|
|
755
|
+
anchor.picture.hyperlinks = {tooltip: hyperlinks.tooltip, rId: hyperlinkRId};
|
|
756
|
+
drawing.rels.push({
|
|
757
|
+
Id: hyperlinkRId,
|
|
758
|
+
Type: RelType.Hyperlink,
|
|
759
|
+
Target: hyperlinks.hyperlink,
|
|
760
|
+
TargetMode: 'External',
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
drawing.anchors.push(anchor);
|
|
764
|
+
});
|
|
765
|
+
this._workbook.drawings.push(drawing);
|
|
766
|
+
|
|
767
|
+
xmlBuffer.reset();
|
|
768
|
+
xmlBuffer.addText(`<drawing r:id="${rId}"/>`);
|
|
769
|
+
this.stream.write(xmlBuffer);
|
|
770
|
+
}
|
|
771
|
+
|
|
702
772
|
_writeLegacyData() {
|
|
703
773
|
if (this.hasComments) {
|
|
704
774
|
xmlBuffer.reset();
|
package/lib/xlsx/rel-type.js
CHANGED
|
@@ -12,6 +12,7 @@ module.exports = {
|
|
|
12
12
|
CoreProperties: 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties',
|
|
13
13
|
ExtenderProperties: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties',
|
|
14
14
|
Comments: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
|
|
15
|
+
Drawing: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
|
|
15
16
|
VmlDrawing: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
|
|
16
17
|
Table: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table',
|
|
17
18
|
PivotCacheDefinition: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition',
|
|
@@ -4,6 +4,9 @@ const colCache = require('../../../utils/col-cache');
|
|
|
4
4
|
const BaseXform = require('../base-xform');
|
|
5
5
|
const Range = require('../../../doc/range');
|
|
6
6
|
|
|
7
|
+
// sqref ranges with more cells than this are kept as a single range-keyed model entry
|
|
8
|
+
const MAX_EXPANDED_RANGE_CELLS = 1000;
|
|
9
|
+
|
|
7
10
|
function assign(definedName, attributes, name, defaultValue) {
|
|
8
11
|
const value = attributes[name];
|
|
9
12
|
if (value !== undefined) {
|
|
@@ -215,12 +218,20 @@ class DataValidationsXform extends BaseXform {
|
|
|
215
218
|
}
|
|
216
219
|
// The four known cases: 1. E4:L9 N4:U9 2.E4 L9 3. N4:U9 4. E4
|
|
217
220
|
const list = this._address.split(/\s+/g) || [];
|
|
221
|
+
// Large ranges (e.g. a whole column 'A2:A1048576') are kept as one range-keyed entry
|
|
222
|
+
// instead of being expanded into one entry per cell, which could take minutes and
|
|
223
|
+
// gigabytes (exceljs#1842). DataValidations.find() resolves them. Small ranges are
|
|
224
|
+
// still expanded so the model keeps its per-cell shape and lookups stay O(1).
|
|
218
225
|
list.forEach(addr => {
|
|
219
226
|
if (addr.includes(':')) {
|
|
220
227
|
const range = new Range(addr);
|
|
221
|
-
range.
|
|
222
|
-
this.model[
|
|
223
|
-
}
|
|
228
|
+
if (range.count > MAX_EXPANDED_RANGE_CELLS) {
|
|
229
|
+
this.model[range.range] = this._dataValidation;
|
|
230
|
+
} else {
|
|
231
|
+
range.forEachAddress(address => {
|
|
232
|
+
this.model[address] = this._dataValidation;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
224
235
|
} else {
|
|
225
236
|
this.model[addr] = this._dataValidation;
|
|
226
237
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exceljs-lts",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.5",
|
|
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",
|