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.
Files changed (36) hide show
  1. package/README.md +28 -11
  2. package/README_zh.md +18 -10
  3. package/dist/es5/doc/cell.js +37 -12
  4. package/dist/es5/doc/cell.js.map +1 -1
  5. package/dist/es5/doc/data-validations.js +94 -1
  6. package/dist/es5/doc/data-validations.js.map +1 -1
  7. package/dist/es5/stream/xlsx/workbook-reader.js +2 -1
  8. package/dist/es5/stream/xlsx/workbook-reader.js.map +1 -1
  9. package/dist/es5/stream/xlsx/workbook-writer.js +21 -1
  10. package/dist/es5/stream/xlsx/workbook-writer.js.map +1 -1
  11. package/dist/es5/stream/xlsx/worksheet-reader.js +1 -1
  12. package/dist/es5/stream/xlsx/worksheet-reader.js.map +1 -1
  13. package/dist/es5/stream/xlsx/worksheet-writer.js +87 -1
  14. package/dist/es5/stream/xlsx/worksheet-writer.js.map +1 -1
  15. package/dist/es5/xlsx/rel-type.js +1 -0
  16. package/dist/es5/xlsx/rel-type.js.map +1 -1
  17. package/dist/es5/xlsx/xform/sheet/data-validations-xform.js +14 -3
  18. package/dist/es5/xlsx/xform/sheet/data-validations-xform.js.map +1 -1
  19. package/dist/exceljs.bare.js +148 -18
  20. package/dist/exceljs.bare.js.map +5 -5
  21. package/dist/exceljs.bare.min.js +1 -1
  22. package/dist/exceljs.bare.min.js.map +1 -1
  23. package/dist/exceljs.js +148 -18
  24. package/dist/exceljs.js.map +5 -5
  25. package/dist/exceljs.min.js +4 -4
  26. package/dist/exceljs.min.js.map +1 -1
  27. package/index.d.ts +36 -3
  28. package/lib/doc/cell.js +37 -12
  29. package/lib/doc/data-validations.js +83 -1
  30. package/lib/stream/xlsx/workbook-reader.js +6 -1
  31. package/lib/stream/xlsx/workbook-writer.js +19 -0
  32. package/lib/stream/xlsx/worksheet-reader.js +1 -1
  33. package/lib/stream/xlsx/worksheet-writer.js +71 -1
  34. package/lib/xlsx/rel-type.js +1 -0
  35. package/lib/xlsx/xform/sheet/data-validations-xform.js +14 -3
  36. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -1,4 +1,24 @@
1
- declare interface Buffer extends ArrayBuffer { }
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: Buffer, options?: Partial<XlsxReadOptions>): Promise<Workbook>;
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.style;
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.style.numFmt;
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.style.font;
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.style.alignment;
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.style.border;
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.style.fill;
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.style.protection;
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.style;
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.style === oldStyle)) {
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.style === oldStyle) {
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.style;
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.style = value.style;
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
- return this.model[address];
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 (this.sharedStrings && this.workbookRels) {
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 = {
@@ -283,7 +283,7 @@ class WorksheetReader extends EventEmitter {
283
283
  if (c.s) {
284
284
  const style = styles.getStyleModel(c.s);
285
285
  if (style) {
286
- cell.style = style;
286
+ cell._setSharedStyle(style);
287
287
  }
288
288
  }
289
289
 
@@ -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
- addBackgroundImage(imageId) {
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();
@@ -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.forEachAddress(address => {
222
- this.model[address] = this._dataValidation;
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.4",
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",