@xlvane/compat 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.d.ts +20 -6
- package/dist/index.js +89 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ 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
|
|
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
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
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.
|
|
@@ -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
|
@@ -2700,6 +2700,20 @@ export declare class Column {
|
|
|
2700
2700
|
get protection(): Partial<Protection> | undefined;
|
|
2701
2701
|
set protection(v: Partial<Protection> | undefined);
|
|
2702
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
|
+
}
|
|
2703
2717
|
/**
|
|
2704
2718
|
* A worksheet, as exceljs `Worksheet` (lib/doc/worksheet.js).
|
|
2705
2719
|
*
|
|
@@ -2761,12 +2775,10 @@ export declare class Worksheet {
|
|
|
2761
2775
|
/** @internal */
|
|
2762
2776
|
_columns: Column[];
|
|
2763
2777
|
/** @internal */
|
|
2764
|
-
_merges:
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
right: number;
|
|
2769
|
-
}>;
|
|
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;
|
|
2770
2782
|
private readonly _keys;
|
|
2771
2783
|
private _name;
|
|
2772
2784
|
/** @internal Use `workbook.addWorksheet()`. */
|
|
@@ -2929,6 +2941,8 @@ export declare class Worksheet {
|
|
|
2929
2941
|
mergeCellsWithoutStyle(...args: unknown[]): void;
|
|
2930
2942
|
private _mergeKeepStyle;
|
|
2931
2943
|
mergeCells(...args: unknown[]): void;
|
|
2944
|
+
/** The index of merged cells, rebuilt from `_merges` after any other change to it. */
|
|
2945
|
+
private _mergedIndex;
|
|
2932
2946
|
unMergeCells(...args: unknown[]): void;
|
|
2933
2947
|
get hasMerges(): boolean;
|
|
2934
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;
|
|
@@ -3628,6 +3628,22 @@ function parseRangeArgs(args) {
|
|
|
3628
3628
|
right: Math.max(s.col, e.col)
|
|
3629
3629
|
};
|
|
3630
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
|
+
};
|
|
3631
3647
|
/**
|
|
3632
3648
|
* A worksheet, as exceljs `Worksheet` (lib/doc/worksheet.js).
|
|
3633
3649
|
*
|
|
@@ -3673,7 +3689,10 @@ var Worksheet = class {
|
|
|
3673
3689
|
/** @internal */
|
|
3674
3690
|
_columns = [];
|
|
3675
3691
|
/** @internal */
|
|
3676
|
-
_merges =
|
|
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;
|
|
3677
3696
|
_keys = /* @__PURE__ */ new Map();
|
|
3678
3697
|
_name = "";
|
|
3679
3698
|
/** @internal Use `workbook.addWorksheet()`. */
|
|
@@ -4378,7 +4397,8 @@ var Worksheet = class {
|
|
|
4378
4397
|
_mergeKeepStyle = false;
|
|
4379
4398
|
mergeCells(...args) {
|
|
4380
4399
|
const range = parseRangeArgs(args);
|
|
4381
|
-
|
|
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");
|
|
4382
4402
|
const master = this.getCell(range.top, range.left);
|
|
4383
4403
|
for (let r = range.top; r <= range.bottom; r++) for (let c = range.left; c <= range.right; c++) {
|
|
4384
4404
|
if (r === range.top && c === range.left) continue;
|
|
@@ -4388,6 +4408,17 @@ var Worksheet = class {
|
|
|
4388
4408
|
}
|
|
4389
4409
|
master._mergeCount++;
|
|
4390
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;
|
|
4391
4422
|
}
|
|
4392
4423
|
unMergeCells(...args) {
|
|
4393
4424
|
const range = parseRangeArgs(args);
|
|
@@ -4575,7 +4606,11 @@ const INJECTION_START = /* @__PURE__ */ new Set([
|
|
|
4575
4606
|
"=",
|
|
4576
4607
|
"+",
|
|
4577
4608
|
"-",
|
|
4578
|
-
"@"
|
|
4609
|
+
"@",
|
|
4610
|
+
"﹦",
|
|
4611
|
+
"﹢",
|
|
4612
|
+
"﹣",
|
|
4613
|
+
"﹫"
|
|
4579
4614
|
]);
|
|
4580
4615
|
function invalid(name, message, hint) {
|
|
4581
4616
|
return new XlvError("XLV_E_INVALID_OPTION", message, {
|
|
@@ -5032,7 +5067,7 @@ function worksheetCsv(sheet, options) {
|
|
|
5032
5067
|
const escape = (out) => {
|
|
5033
5068
|
if (injection !== "escape" || typeof out !== "string") return [out, false];
|
|
5034
5069
|
const first = out.replace(/\0/g, "")[0];
|
|
5035
|
-
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];
|
|
5036
5071
|
};
|
|
5037
5072
|
if (f.headers) lines.push(f.headers.map((h, i) => {
|
|
5038
5073
|
const [out, force] = escape(h);
|
|
@@ -5985,6 +6020,24 @@ var Workbook = class {
|
|
|
5985
6020
|
_fill(read, options = {}) {
|
|
5986
6021
|
const ignore = new Set(options.ignoreNodes ?? []);
|
|
5987
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;
|
|
5988
6041
|
for (const s of read.sheets) {
|
|
5989
6042
|
if (s.kind !== "worksheet" || options.maxRows === void 0 && options.maxCols === void 0 || !use("sheetData")) continue;
|
|
5990
6043
|
const perRow = /* @__PURE__ */ new Map();
|
|
@@ -6055,6 +6108,7 @@ var Workbook = class {
|
|
|
6055
6108
|
ws.orderNo = position;
|
|
6056
6109
|
for (const c of use("cols") ? s.columns : []) {
|
|
6057
6110
|
const last = Math.min(c.max, 16384);
|
|
6111
|
+
spend(Math.max(0, last - c.min + 1), "The column definitions", s.name);
|
|
6058
6112
|
for (let n = c.min; n <= last; n++) {
|
|
6059
6113
|
const column = ws.getColumn(n);
|
|
6060
6114
|
if (c.width !== null) column.width = c.width;
|
|
@@ -6108,6 +6162,15 @@ var Workbook = class {
|
|
|
6108
6162
|
}
|
|
6109
6163
|
const dim = /(\d+)$/.exec(s.dimension ?? "");
|
|
6110
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
|
+
});
|
|
6111
6174
|
for (let r = 1; r <= lastRow; r++) {
|
|
6112
6175
|
const info = s.rowInfo(r);
|
|
6113
6176
|
if (!info) continue;
|
|
@@ -6117,7 +6180,11 @@ var Workbook = class {
|
|
|
6117
6180
|
if (info.outlineLevel) row.outlineLevel = info.outlineLevel;
|
|
6118
6181
|
}
|
|
6119
6182
|
for (const note of s.notes) ws.getCell(note.ref).note = noteOut(note);
|
|
6120
|
-
for (const m of use("mergeCells") ? s.merges : [])
|
|
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
|
+
}
|
|
6121
6188
|
if (use("sheetViews")) ws.views = [viewOut(s.view, s.freeze)];
|
|
6122
6189
|
const tab = colorOut(s.tabColor ?? void 0);
|
|
6123
6190
|
if (tab && use("sheetPr")) ws.properties["tabColor"] = tab;
|
|
@@ -6128,7 +6195,10 @@ var Workbook = class {
|
|
|
6128
6195
|
if (use("pageSetup")) ws.pageSetup = pageSetupOut(s.pageSetup, s.printArea, s.printTitles);
|
|
6129
6196
|
if (use("headerFooter")) ws.headerFooter = headerFooterOut(s.headerFooter);
|
|
6130
6197
|
if (use("rowBreaks")) ws.rowBreaks = rowBreaksOut(s.pageBreaks.rows);
|
|
6131
|
-
for (const def of use("tableParts") ? s.tables : [])
|
|
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
|
+
}
|
|
6132
6202
|
for (const img of use("drawing") ? s.images : []) {
|
|
6133
6203
|
const id = img.image === null ? void 0 : mediaIds.get(img.image);
|
|
6134
6204
|
const media = id === void 0 ? null : imageOut(ws, img, id);
|
|
@@ -6166,6 +6236,18 @@ function freeIds(wanted) {
|
|
|
6166
6236
|
return next;
|
|
6167
6237
|
});
|
|
6168
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
|
+
}
|
|
6169
6251
|
// src/stream.ts
|
|
6170
6252
|
/**
|
|
6171
6253
|
* A cell value for the streaming writer. Rows are written as they are committed, before later
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xlvane/compat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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.
|
|
75
|
+
"xlvane": "^1.2.0"
|
|
76
76
|
},
|
|
77
77
|
"publishConfig": {
|
|
78
78
|
"access": "public",
|