@xlvane/compat 1.0.1 → 1.1.1

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 CHANGED
@@ -56,9 +56,9 @@ In CommonJS, `const ExcelJS = require('exceljs');` gives the same object; call `
56
56
  The full list, with the exceljs source of each behaviour, is in [docs/exceljs-compat.md](https://github.com/DevXord/xlvane/blob/main/docs/exceljs-compat.md) (also in `node_modules/xlvane/docs/exceljs-compat.md` after installing). The ones most likely to matter:
57
57
 
58
58
  - **Not offered:** row and column operations on a streamed worksheet (`insertRow(s)`, `spliceRows`, `duplicateRow`, `spliceColumns`; exceljs's streaming worksheet does not have them either) throw an `XlvError` with code `XLV_E_NOT_SUPPORTED` and the name of the method. Deep imports of exceljs internals (`exceljs/lib/...`) do not resolve; `exceljs/dist/es5` and the `exceljs/dist/exceljs*.js` files do. Nothing is dropped silently.
59
- - **CSV:** text that starts with `=`, `+`, `-`, `@`, a tab, CR or LF is written with a leading `'`, so spreadsheet applications do not run it as a formula. `formulaInjection: "off"` writes it unchanged, as exceljs does.
60
- - **Sheet ids:** the file gets `sheetId` 1, 2, 3… in sheet order, and a loaded workbook numbers its sheets 1, 2, 3… in file order. After removing a sheet, `getWorksheet(id)` can give another sheet than with exceljs; look sheets up by name.
61
- - **Views:** only the first view of a worksheet is written; a split view is written without its `topLeftCell`.
59
+ - **CSV:** text that starts with `=`, `+`, `-`, `@`, a tab, CR, LF or another control character, or with a full-width or small form of these signs, is written with a leading `'`, so spreadsheet applications do not run it as a formula. `formulaInjection: "off"` writes it unchanged, as exceljs does.
60
+ - **Sheet ids:** `worksheet.id` is written as `sheetId` and kept on loading, as in exceljs; an id outside 1 to 65534 is written as the lowest unused one, and the streaming `WorkbookReader` numbers worksheets 1, 2, 3… in file order.
61
+ - **Views:** only the first view of a worksheet is written; a frozen view always gets the cell right of and below the panes as `topLeftCell`.
62
62
  - **Notes:** `protection.locked` and `lockText` read back as `'True'`/`'False'`, the values Excel writes.
63
63
  - **Module object:** it is frozen, so adding or replacing its properties (monkey-patching) has no effect.
64
64
  - **Streaming writer speed:** `stream.xlsx.WorkbookWriter` compresses in JavaScript (fflate) and took about twice as long as exceljs, which uses Node.js zlib (300 000 rows: 6.1 s against 2.8 s).
@@ -66,6 +66,7 @@ The full list, with the exceljs source of each behaviour, is in [docs/exceljs-co
66
66
 
67
67
  ## Links
68
68
 
69
+ - Website: [xlvane.github.io](https://xlvane.github.io)
69
70
  - Changes in each version: [CHANGELOG.md](https://github.com/DevXord/xlvane/blob/main/CHANGELOG.md)
70
71
  - Bugs and questions: [GitHub issues](https://github.com/DevXord/xlvane/issues)
71
72
  - Reporting a vulnerability: [SECURITY.md](https://github.com/DevXord/xlvane/blob/main/SECURITY.md)
package/dist/index.d.ts CHANGED
@@ -295,6 +295,8 @@ export declare class Workbook {
295
295
  get nextId(): number;
296
296
  /** Adds a worksheet. Names follow exceljs rules (31 characters at most; longer names are cut). */
297
297
  addWorksheet(name?: string, options?: WorksheetOptions): Worksheet;
298
+ /** @internal Adds a worksheet with a given id (a loaded file's sheetId, a model's id; D-F03). */
299
+ _addWorksheetWithId(id: number, name?: string, options?: WorksheetOptions): Worksheet;
298
300
  removeWorksheet(id: number | string): void;
299
301
  /** Removes a worksheet by id or name, as `removeWorksheet` (lib/doc/workbook.js L77). */
300
302
  removeWorksheetEx(worksheet: Worksheet): void;
@@ -2698,6 +2700,20 @@ export declare class Column {
2698
2700
  get protection(): Partial<Protection> | undefined;
2699
2701
  set protection(v: Partial<Protection> | undefined);
2700
2702
  }
2703
+ /** A merged range of a worksheet. */
2704
+ type MergeRange = {
2705
+ top: number;
2706
+ left: number;
2707
+ bottom: number;
2708
+ right: number;
2709
+ };
2710
+ /** The merged ranges of a worksheet by master address, with a counter of changes (D-H01). */
2711
+ declare class MergeMap extends Map<string, MergeRange> {
2712
+ version: number;
2713
+ override set(key: string, value: MergeRange): this;
2714
+ override delete(key: string): boolean;
2715
+ override clear(): void;
2716
+ }
2701
2717
  /**
2702
2718
  * A worksheet, as exceljs `Worksheet` (lib/doc/worksheet.js).
2703
2719
  *
@@ -2759,12 +2775,10 @@ export declare class Worksheet {
2759
2775
  /** @internal */
2760
2776
  _columns: Column[];
2761
2777
  /** @internal */
2762
- _merges: Map<string, {
2763
- top: number;
2764
- left: number;
2765
- bottom: number;
2766
- right: number;
2767
- }>;
2778
+ _merges: MergeMap;
2779
+ /** The cells covered by `_merges` (row × 16 385 + column), valid while `_mergedVersion` equals `_merges.version`. */
2780
+ private _mergedCells;
2781
+ private _mergedVersion;
2768
2782
  private readonly _keys;
2769
2783
  private _name;
2770
2784
  /** @internal Use `workbook.addWorksheet()`. */
@@ -2927,6 +2941,8 @@ export declare class Worksheet {
2927
2941
  mergeCellsWithoutStyle(...args: unknown[]): void;
2928
2942
  private _mergeKeepStyle;
2929
2943
  mergeCells(...args: unknown[]): void;
2944
+ /** The index of merged cells, rebuilt from `_merges` after any other change to it. */
2945
+ private _mergedIndex;
2930
2946
  unMergeCells(...args: unknown[]): void;
2931
2947
  get hasMerges(): boolean;
2932
2948
  /** Adds conditional formatting for a range (exceljs `addConditionalFormatting`). */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { XlvError, adjustFormula, adjustRange, createWorkbook, hashPassword, imageFormat, openWorkbookStream, readWorkbook, shiftFormula, writeWorkbook, writeWorkbookStream } from "xlvane";
1
+ import { DEFAULT_READ_LIMITS, XlvError, adjustFormula, adjustRange, createWorkbook, hashPassword, imageFormat, openWorkbookStream, readWorkbook, shiftFormula, writeWorkbook, writeWorkbookStream } from "xlvane";
2
2
  // src/media.ts
3
3
  const MAX_COLS = 16384;
4
4
  const MAX_ROWS = 1048576;
@@ -1228,9 +1228,10 @@ function noteBoxOut(box) {
1228
1228
  * The first exceljs view and the tab colour (X4-11, X4-12). xlvane writes one view: frozen panes
1229
1229
  * (`xSplit`/`ySplit` in columns and rows) or a split window (`xSplit`/`ySplit` in 1/20 point, as
1230
1230
  * exceljs), plus `zoomScale`, `showGridLines`, `showRowColHeaders`, `rightToLeft` and `style`.
1231
- * `activeCell` is written for every view; `topLeftCell` only for a normal view (a split view's
1232
- * exceljs `topLeftCell` is `pane@topLeftCell`, which the core does not write); `activePane`
1233
- * follows from the panes.
1231
+ * `activeCell` is written for every view; `topLeftCell` for a normal view as
1232
+ * `sheetView@topLeftCell` and for a split view as `pane@topLeftCell`, as exceljs writes them
1233
+ * (sheet-view-xform.js L72-85; D-F02); a frozen view's `topLeftCell` follows from the panes;
1234
+ * `activePane` follows from the panes.
1234
1235
  */
1235
1236
  function layoutIn(ws) {
1236
1237
  const v = ws.views[0];
@@ -1251,10 +1252,14 @@ function layoutIn(ws) {
1251
1252
  rows: v.ySplit ?? 0,
1252
1253
  cols: v.xSplit ?? 0
1253
1254
  };
1254
- else if (v.state === "split" && ((v.xSplit ?? 0) > 0 || (v.ySplit ?? 0) > 0)) view.split = {
1255
- x: (v.xSplit ?? 0) / 20,
1256
- y: (v.ySplit ?? 0) / 20
1257
- };
1255
+ else if (v.state === "split" && ((v.xSplit ?? 0) > 0 || (v.ySplit ?? 0) > 0)) {
1256
+ const corner = typeof v["topLeftCell"] === "string" && v["topLeftCell"] !== "" ? { topLeftCell: v["topLeftCell"] } : {};
1257
+ view.split = {
1258
+ x: (v.xSplit ?? 0) / 20,
1259
+ y: (v.ySplit ?? 0) / 20,
1260
+ ...corner
1261
+ };
1262
+ }
1258
1263
  }
1259
1264
  let tab = ws.properties["tabColor"];
1260
1265
  if (tab && typeof tab.argb === "string" && /^[0-9A-Fa-f]{7}$/.test(tab.argb)) tab = {
@@ -1356,7 +1361,10 @@ function toNewWorkbook(sheets, date1904, meta = {}) {
1356
1361
  if (meta.views && meta.views.length > 0) book.views(bookViewsIn(meta.views));
1357
1362
  if (meta.calcProperties?.["fullCalcOnLoad"]) book.calculation({ fullCalcOnLoad: true });
1358
1363
  for (const ws of sheets) at(ws.name, void 0, () => {
1359
- const sheet = book.addSheet(ws.name, { state: ws.state });
1364
+ const sheet = book.addSheet(ws.name, {
1365
+ state: ws.state,
1366
+ ...sheetIdOf(ws.id)
1367
+ });
1360
1368
  const useStyles = meta.useStyles !== false;
1361
1369
  const sharedRanges = sharedFormulaRanges(ws);
1362
1370
  const props = sheetPropertiesIn(ws.properties);
@@ -1487,8 +1495,9 @@ function fontOut(f) {
1487
1495
  /**
1488
1496
  * An exceljs view from the view read by xlvane, shaped as exceljs reads one (sheet-view-xform.js
1489
1497
  * L145–192; X4-11): a missing `zoomScaleNormal` reads as 100; a frozen view gets the default
1490
- * `topLeftCell`; a split view gets `xSplit`/`ySplit` in 1/20 point and the active pane xlvane
1491
- * writes; a normal view has no `topLeftCell`, as in exceljs.
1498
+ * `topLeftCell`; a split view gets `xSplit`/`ySplit` in 1/20 point, `pane@topLeftCell` as
1499
+ * `topLeftCell` (sheet-view-xform.js L118-124) and the active pane xlvane writes; a normal view
1500
+ * has no `topLeftCell`, as in exceljs.
1492
1501
  */
1493
1502
  function viewOut(view, freeze) {
1494
1503
  const common = {
@@ -1516,14 +1525,14 @@ function viewOut(view, freeze) {
1516
1525
  }
1517
1526
  const active = view.activeCell !== null ? { activeCell: view.activeCell } : {};
1518
1527
  if (view.split) {
1519
- const { x, y } = view.split;
1528
+ const { x, y, topLeftCell } = view.split;
1520
1529
  const activePane = x > 0 && y > 0 ? "bottomRight" : y > 0 ? "bottomLeft" : "topRight";
1521
1530
  return {
1522
1531
  ...common,
1523
1532
  state: "split",
1524
1533
  xSplit: Math.round(x * 20),
1525
1534
  ySplit: Math.round(y * 20),
1526
- topLeftCell: void 0,
1535
+ topLeftCell,
1527
1536
  activePane,
1528
1537
  ...active,
1529
1538
  ...style
@@ -1665,6 +1674,10 @@ function styleOut(s, numFmt) {
1665
1674
  function readBytes(bytes) {
1666
1675
  return readWorkbook(bytes);
1667
1676
  }
1677
+ /** `{ sheetId }` for a worksheet id that Office accepts (1 to 65534, W2-20), otherwise nothing (D-F03). */
1678
+ function sheetIdOf(id) {
1679
+ return Number.isInteger(id) && id >= 1 && id <= 65534 ? { sheetId: id } : {};
1680
+ }
1668
1681
  // src/sheet-rules.ts
1669
1682
  const MAX_ROW = 1048576;
1670
1683
  const MAX_COL = 16384;
@@ -3615,6 +3628,22 @@ function parseRangeArgs(args) {
3615
3628
  right: Math.max(s.col, e.col)
3616
3629
  };
3617
3630
  }
3631
+ /** The merged ranges of a worksheet by master address, with a counter of changes (D-H01). */
3632
+ var MergeMap = class extends Map {
3633
+ version = 0;
3634
+ set(key, value) {
3635
+ this.version++;
3636
+ return super.set(key, value);
3637
+ }
3638
+ delete(key) {
3639
+ this.version++;
3640
+ return super.delete(key);
3641
+ }
3642
+ clear() {
3643
+ this.version++;
3644
+ super.clear();
3645
+ }
3646
+ };
3618
3647
  /**
3619
3648
  * A worksheet, as exceljs `Worksheet` (lib/doc/worksheet.js).
3620
3649
  *
@@ -3660,7 +3689,10 @@ var Worksheet = class {
3660
3689
  /** @internal */
3661
3690
  _columns = [];
3662
3691
  /** @internal */
3663
- _merges = /* @__PURE__ */ new Map();
3692
+ _merges = new MergeMap();
3693
+ /** The cells covered by `_merges` (row × 16 385 + column), valid while `_mergedVersion` equals `_merges.version`. */
3694
+ _mergedCells = /* @__PURE__ */ new Set();
3695
+ _mergedVersion = -1;
3664
3696
  _keys = /* @__PURE__ */ new Map();
3665
3697
  _name = "";
3666
3698
  /** @internal Use `workbook.addWorksheet()`. */
@@ -4365,7 +4397,8 @@ var Worksheet = class {
4365
4397
  _mergeKeepStyle = false;
4366
4398
  mergeCells(...args) {
4367
4399
  const range = parseRangeArgs(args);
4368
- for (const m of this._merges.values()) if (range.top <= m.bottom && m.top <= range.bottom && range.left <= m.right && m.left <= range.right) throw new Error("Cannot merge already merged cells");
4400
+ const merged = this._mergedIndex();
4401
+ for (let r = range.top; r <= range.bottom; r++) for (let c = range.left; c <= range.right; c++) if (merged.has(r * 16385 + c)) throw new Error("Cannot merge already merged cells");
4369
4402
  const master = this.getCell(range.top, range.left);
4370
4403
  for (let r = range.top; r <= range.bottom; r++) for (let c = range.left; c <= range.right; c++) {
4371
4404
  if (r === range.top && c === range.left) continue;
@@ -4375,6 +4408,17 @@ var Worksheet = class {
4375
4408
  }
4376
4409
  master._mergeCount++;
4377
4410
  this._merges.set(`${columnLetter(range.left)}${String(range.top)}`, range);
4411
+ for (let r = range.top; r <= range.bottom; r++) for (let c = range.left; c <= range.right; c++) merged.add(r * 16385 + c);
4412
+ this._mergedVersion = this._merges.version;
4413
+ }
4414
+ /** The index of merged cells, rebuilt from `_merges` after any other change to it. */
4415
+ _mergedIndex() {
4416
+ if (this._mergedVersion !== this._merges.version) {
4417
+ this._mergedCells = /* @__PURE__ */ new Set();
4418
+ for (const m of this._merges.values()) for (let r = m.top; r <= m.bottom; r++) for (let c = m.left; c <= m.right; c++) this._mergedCells.add(r * 16385 + c);
4419
+ this._mergedVersion = this._merges.version;
4420
+ }
4421
+ return this._mergedCells;
4378
4422
  }
4379
4423
  unMergeCells(...args) {
4380
4424
  const range = parseRangeArgs(args);
@@ -4562,7 +4606,11 @@ const INJECTION_START = /* @__PURE__ */ new Set([
4562
4606
  "=",
4563
4607
  "+",
4564
4608
  "-",
4565
- "@"
4609
+ "@",
4610
+ "﹦",
4611
+ "﹢",
4612
+ "﹣",
4613
+ "﹫"
4566
4614
  ]);
4567
4615
  function invalid(name, message, hint) {
4568
4616
  return new XlvError("XLV_E_INVALID_OPTION", message, {
@@ -5019,7 +5067,7 @@ function worksheetCsv(sheet, options) {
5019
5067
  const escape = (out) => {
5020
5068
  if (injection !== "escape" || typeof out !== "string") return [out, false];
5021
5069
  const first = out.replace(/\0/g, "")[0];
5022
- return first !== void 0 && INJECTION_START.has(first) ? [`'${out}`, f.quote !== ""] : [out, false];
5070
+ return first !== void 0 && (INJECTION_START.has(first) || first.charCodeAt(0) < 32) ? [`'${out}`, f.quote !== ""] : [out, false];
5023
5071
  };
5024
5072
  if (f.headers) lines.push(f.headers.map((h, i) => {
5025
5073
  const [out, force] = escape(h);
@@ -5844,7 +5892,10 @@ var Workbook = class {
5844
5892
  }
5845
5893
  /** Adds a worksheet. Names follow exceljs rules (31 characters at most; longer names are cut). */
5846
5894
  addWorksheet(name, options) {
5847
- const id = this.nextId;
5895
+ return this._addWorksheetWithId(this.nextId, name, options);
5896
+ }
5897
+ /** @internal Adds a worksheet with a given id (a loaded file's sheetId, a model's id; D-F03). */
5898
+ _addWorksheetWithId(id, name, options) {
5848
5899
  const sheet = new Worksheet(this, id, name, options);
5849
5900
  sheet.orderNo = this._sheets.reduce((acc, ws) => ws?.orderNo !== void 0 && ws.orderNo > acc ? ws.orderNo : acc, 0) + 1;
5850
5901
  this._sheets[id] = sheet;
@@ -5916,8 +5967,10 @@ var Workbook = class {
5916
5967
  this.views = Array.isArray(value["views"]) ? [...value["views"]] : [];
5917
5968
  this._media = Array.isArray(value["media"]) ? [...value["media"]] : [];
5918
5969
  for (const ws of this.worksheets) this.removeWorksheet(ws.id);
5919
- for (const m of Array.isArray(value["worksheets"]) ? value["worksheets"] : []) {
5920
- const ws = this.addWorksheet(String(m["name"] ?? ""), { state: m["state"] ?? "visible" });
5970
+ const models = Array.isArray(value["worksheets"]) ? value["worksheets"] : [];
5971
+ const ids = freeIds(models.map((m) => m["id"]));
5972
+ for (const [i, m] of models.entries()) {
5973
+ const ws = this._addWorksheetWithId(ids[i] ?? this.nextId, String(m["name"] ?? ""), { state: m["state"] ?? "visible" });
5921
5974
  ws.model = m;
5922
5975
  }
5923
5976
  this._definedNames.model = Array.isArray(value["definedNames"]) ? value["definedNames"] : [];
@@ -5967,6 +6020,24 @@ var Workbook = class {
5967
6020
  _fill(read, options = {}) {
5968
6021
  const ignore = new Set(options.ignoreNodes ?? []);
5969
6022
  const use = (node) => !ignore.has(node);
6023
+ let cellsRead = 0;
6024
+ for (const s of read.sheets) cellsRead += s.cellCount;
6025
+ const budget = Math.max(DEFAULT_READ_LIMITS.maxCells, 16 * cellsRead);
6026
+ let spent = 0;
6027
+ const spend = (positions, what, sheet) => {
6028
+ spent += positions;
6029
+ if (spent <= budget) return;
6030
+ throw new XlvError("XLV_E_LIMIT_EXCEEDED", `${what} of worksheet "${sheet}" would create more than ${String(budget)} cells or columns for ${String(cellsRead)} cells in the file.`, {
6031
+ location: { sheet },
6032
+ hint: "The file declares ranges far larger than its data. Load it with ignoreNodes for that element (for example ignoreNodes: [\"mergeCells\"]), or read it with xlvane's readWorkbook, which does not create these objects.",
6033
+ details: {
6034
+ limit: "maxCells",
6035
+ value: budget
6036
+ }
6037
+ });
6038
+ };
6039
+ const rowBudget = Math.max(67108864, 16 * cellsRead);
6040
+ let rowsLooked = 0;
5970
6041
  for (const s of read.sheets) {
5971
6042
  if (s.kind !== "worksheet" || options.maxRows === void 0 && options.maxCols === void 0 || !use("sheetData")) continue;
5972
6043
  const perRow = /* @__PURE__ */ new Map();
@@ -6030,12 +6101,14 @@ var Workbook = class {
6030
6101
  name: d.name,
6031
6102
  ranges: extractRanges(d.refersTo)
6032
6103
  }));
6104
+ const ids = freeIds(read.sheets.map((s) => s.kind === "worksheet" ? s.sheetId : void 0));
6033
6105
  for (const [position, s] of read.sheets.entries()) {
6034
6106
  if (s.kind !== "worksheet") continue;
6035
- const ws = this.addWorksheet(s.name, { state: s.state });
6107
+ const ws = this._addWorksheetWithId(ids[position] ?? this.nextId, s.name, { state: s.state });
6036
6108
  ws.orderNo = position;
6037
6109
  for (const c of use("cols") ? s.columns : []) {
6038
6110
  const last = Math.min(c.max, 16384);
6111
+ spend(Math.max(0, last - c.min + 1), "The column definitions", s.name);
6039
6112
  for (let n = c.min; n <= last; n++) {
6040
6113
  const column = ws.getColumn(n);
6041
6114
  if (c.width !== null) column.width = c.width;
@@ -6089,6 +6162,15 @@ var Workbook = class {
6089
6162
  }
6090
6163
  const dim = /(\d+)$/.exec(s.dimension ?? "");
6091
6164
  lastRow = use("sheetData") ? Math.min(Math.max(lastRow, dim ? Number(dim[1]) : 0), 1048576) : 0;
6165
+ rowsLooked += lastRow;
6166
+ if (rowsLooked > rowBudget) throw new XlvError("XLV_E_LIMIT_EXCEEDED", `The rows of worksheet "${s.name}" up to its dimension or last cell would bring the rows looked at for their settings past ${String(rowBudget)} for ${String(cellsRead)} cells in the file.`, {
6167
+ location: { sheet: s.name },
6168
+ hint: "The sheets declare far more rows than they hold. Load the file with ignoreNodes: [\"sheetData\"] to skip cells and rows, or read it with xlvane's readWorkbook.",
6169
+ details: {
6170
+ limit: "maxCells",
6171
+ value: rowBudget
6172
+ }
6173
+ });
6092
6174
  for (let r = 1; r <= lastRow; r++) {
6093
6175
  const info = s.rowInfo(r);
6094
6176
  if (!info) continue;
@@ -6098,7 +6180,11 @@ var Workbook = class {
6098
6180
  if (info.outlineLevel) row.outlineLevel = info.outlineLevel;
6099
6181
  }
6100
6182
  for (const note of s.notes) ws.getCell(note.ref).note = noteOut(note);
6101
- for (const m of use("mergeCells") ? s.merges : []) if (m.includes(":")) ws.mergeCells(m);
6183
+ for (const m of use("mergeCells") ? s.merges : []) {
6184
+ if (!m.includes(":")) continue;
6185
+ spend(areaOf(m), "A merged range", s.name);
6186
+ ws.mergeCells(m);
6187
+ }
6102
6188
  if (use("sheetViews")) ws.views = [viewOut(s.view, s.freeze)];
6103
6189
  const tab = colorOut(s.tabColor ?? void 0);
6104
6190
  if (tab && use("sheetPr")) ws.properties["tabColor"] = tab;
@@ -6109,7 +6195,10 @@ var Workbook = class {
6109
6195
  if (use("pageSetup")) ws.pageSetup = pageSetupOut(s.pageSetup, s.printArea, s.printTitles);
6110
6196
  if (use("headerFooter")) ws.headerFooter = headerFooterOut(s.headerFooter);
6111
6197
  if (use("rowBreaks")) ws.rowBreaks = rowBreaksOut(s.pageBreaks.rows);
6112
- for (const def of use("tableParts") ? s.tables : []) ws.tables[def.name] = tableOut(ws, def);
6198
+ for (const def of use("tableParts") ? s.tables : []) {
6199
+ spend(areaOf(def.ref), "A table", s.name);
6200
+ ws.tables[def.name] = tableOut(ws, def);
6201
+ }
6113
6202
  for (const img of use("drawing") ? s.images : []) {
6114
6203
  const id = img.image === null ? void 0 : mediaIds.get(img.image);
6115
6204
  const media = id === void 0 ? null : imageOut(ws, img, id);
@@ -6123,6 +6212,42 @@ var Workbook = class {
6123
6212
  }
6124
6213
  }
6125
6214
  };
6215
+ /**
6216
+ * Worksheet ids for a list of wanted ids: each whole number from 1 to 65534 (the sheet ids Office
6217
+ * uses, W2-20) that is not wanted by an earlier entry is kept; other entries (missing, invalid,
6218
+ * larger or repeated ids) get the lowest ids no entry wants, in order. The limit keeps the
6219
+ * `_sheets` array small: an id from an untrusted file such as 4294967295 is not an array index and
6220
+ * 20000000 makes every scan of the array slow (found by review, D-F03). `undefined` entries
6221
+ * (sheets that are not worksheets) get no id.
6222
+ */
6223
+ function freeIds(wanted) {
6224
+ const taken = /* @__PURE__ */ new Set();
6225
+ const kept = wanted.map((w) => {
6226
+ if (typeof w !== "number" || !Number.isInteger(w) || w < 1 || w > 65534 || taken.has(w)) return null;
6227
+ taken.add(w);
6228
+ return w;
6229
+ });
6230
+ let next = 1;
6231
+ return kept.map((k, i) => {
6232
+ if (wanted[i] === void 0) return void 0;
6233
+ if (k !== null) return k;
6234
+ while (taken.has(next)) next++;
6235
+ taken.add(next);
6236
+ return next;
6237
+ });
6238
+ }
6239
+ /** Cells in a range such as "A1:C3" (1 for a cell, 0 for anything else). */
6240
+ function areaOf(ref) {
6241
+ const m = /^([A-Z]{1,3})(\d{1,7})(?::([A-Z]{1,3})(\d{1,7}))?$/.exec(ref.replace(/\$/g, ""));
6242
+ if (!m) return 0;
6243
+ const col = (letters) => {
6244
+ let n = 0;
6245
+ for (const ch of letters) n = n * 26 + ch.charCodeAt(0) - 64;
6246
+ return n;
6247
+ };
6248
+ const [, c1 = "A", r1 = "1", c2 = c1, r2 = r1] = m;
6249
+ return (Math.abs(Number(r2) - Number(r1)) + 1) * (Math.abs(col(c2) - col(c1)) + 1);
6250
+ }
6126
6251
  // src/stream.ts
6127
6252
  /**
6128
6253
  * A cell value for the streaming writer. Rows are written as they are committed, before later
@@ -6284,6 +6409,7 @@ var WorksheetWriter = class extends Worksheet {
6284
6409
  return {
6285
6410
  name: this.name,
6286
6411
  state: this.state,
6412
+ ...sheetIdOf(this.id),
6287
6413
  columns: trimTrailing(this.columns.map((c) => columnIn(c, this.writer._useStyles))),
6288
6414
  ...layout.freeze ? { freeze: layout.freeze } : {},
6289
6415
  ...Object.keys(layout.view).length > 0 ? { view: layout.view } : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xlvane/compat",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "The commonly used exceljs 4.4.0 API on top of xlvane, for switching with one line in package.json.",
5
5
  "keywords": [
6
6
  "xlsx",
@@ -72,7 +72,7 @@
72
72
  "build": "tsdown"
73
73
  },
74
74
  "dependencies": {
75
- "xlvane": "^1.0.1"
75
+ "xlvane": "^1.1.1"
76
76
  },
77
77
  "publishConfig": {
78
78
  "access": "public",