xlvane 1.2.1 → 1.2.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/README.md +3 -3
- package/dist/cli.js +71 -24
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -3
- package/dist/{node-files-CDb6TyZb.js → node-files-Bxzv7THG.js} +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/{options-Ba1iWD-Z.js → options-Cw7OraxE.js} +274 -71
- package/dist/{rows-IWKIxHt9.js → rows-DnlfBgme.js} +104 -23
- package/dist/{stream-write-BGuUjlXo.js → stream-write-DWD8Pflj.js} +81 -15
- package/dist/{stream-write-BVaFkgwO.d.ts → stream-write-y52KUDW8.d.ts} +34 -3
- package/docs/errors.md +6 -2
- package/docs/exceljs-compat.md +2 -2
- package/llms-full.txt +12 -8
- package/package.json +1 -1
|
@@ -266,6 +266,10 @@ function firstLoneSurrogate(s) {
|
|
|
266
266
|
}
|
|
267
267
|
return -1;
|
|
268
268
|
}
|
|
269
|
+
/** Shortens a value from the file for messages and details. */
|
|
270
|
+
function clip(value) {
|
|
271
|
+
return value.length > 200 ? `${value.slice(0, 200)}…` : value;
|
|
272
|
+
}
|
|
269
273
|
// src/xml/parser.ts
|
|
270
274
|
/** XML 1.0 §2.11: CR LF and lone CR become LF. The parser applies this before parsing. */
|
|
271
275
|
function normalizeLineEnds(xml) {
|
|
@@ -1008,10 +1012,10 @@ function decodeXml(bytes, part) {
|
|
|
1008
1012
|
const declared = declaredEncoding(text);
|
|
1009
1013
|
if (declared !== null) {
|
|
1010
1014
|
const normalized = declared.toLowerCase();
|
|
1011
|
-
if (!(encoding === "utf-8" ? normalized === "utf-8" : normalized === "utf-16" || normalized === encoding)) throw new XlvError("XLV_E_XML_ENCODING", `The part declares encoding "${declared}", which is not supported.`, {
|
|
1015
|
+
if (!(encoding === "utf-8" ? normalized === "utf-8" : normalized === "utf-16" || normalized === encoding)) throw new XlvError("XLV_E_XML_ENCODING", `The part declares encoding "${clip(declared)}", which is not supported.`, {
|
|
1012
1016
|
location: { part },
|
|
1013
1017
|
hint: "Office Open XML parts must be encoded in UTF-8 or UTF-16. Re-save the file in a spreadsheet application.",
|
|
1014
|
-
details: { encoding: declared }
|
|
1018
|
+
details: { encoding: clip(declared) }
|
|
1015
1019
|
});
|
|
1016
1020
|
}
|
|
1017
1021
|
return text;
|
|
@@ -1838,6 +1842,12 @@ function readSheetDrawing(pkg, sheetPart, sheet, ids, ctx, media) {
|
|
|
1838
1842
|
};
|
|
1839
1843
|
}
|
|
1840
1844
|
const drawingRels = pkg.relationships(drawing.target);
|
|
1845
|
+
const drawingRelsById = /* @__PURE__ */ new Map();
|
|
1846
|
+
for (const rel of drawingRels) if (!drawingRelsById.has(rel.id)) drawingRelsById.set(rel.id, rel);
|
|
1847
|
+
const relOfType = (id, name) => {
|
|
1848
|
+
const rel = id === void 0 ? void 0 : drawingRelsById.get(id);
|
|
1849
|
+
return rel !== void 0 && classifyRelationship(rel.type)?.name === name ? rel : void 0;
|
|
1850
|
+
};
|
|
1841
1851
|
const images = [];
|
|
1842
1852
|
let other = 0;
|
|
1843
1853
|
const marker = (el) => {
|
|
@@ -1916,9 +1926,8 @@ function readSheetDrawing(pkg, sheetPart, sheet, ids, ctx, media) {
|
|
|
1916
1926
|
}
|
|
1917
1927
|
const nv = childNamed(childNamed(pic, xdr, "nvPicPr") ?? pic, xdr, "cNvPr");
|
|
1918
1928
|
const embed = childNamed(childNamed(pic, xdr, "blipFill") ?? pic, a, "blip")?.attrs.get(`{${r}}embed`);
|
|
1919
|
-
const imageRel =
|
|
1920
|
-
const
|
|
1921
|
-
const linkRel = linkId === void 0 ? void 0 : drawingRels.find((x) => x.id === linkId && classifyRelationship(x.type)?.name === "hyperlink");
|
|
1929
|
+
const imageRel = relOfType(embed, "image");
|
|
1930
|
+
const linkRel = relOfType(nv === void 0 ? void 0 : childNamed(nv, a, "hlinkClick")?.attrs.get(`{${r}}id`), "hyperlink");
|
|
1922
1931
|
images.push(Object.freeze({
|
|
1923
1932
|
name: nv?.attrs.get("name") ?? "",
|
|
1924
1933
|
description: nv?.attrs.get("descr") ?? null,
|
|
@@ -3505,19 +3514,38 @@ function describe$1(value) {
|
|
|
3505
3514
|
/**
|
|
3506
3515
|
* Checks the number of positions an output built from a sheet would have against
|
|
3507
3516
|
* max(`maxCells`, OUTPUT_PER_CELL × cells read). Throws `XLV_E_LIMIT_EXCEEDED` above it.
|
|
3517
|
+
* `hint` replaces the default hint, which suits outputs read from a file.
|
|
3508
3518
|
*/
|
|
3509
|
-
function checkOutputPositions(positions, cells, maxCells, what, sheet) {
|
|
3519
|
+
function checkOutputPositions(positions, cells, maxCells, what, sheet, hint) {
|
|
3510
3520
|
const budget = Math.max(maxCells, 16 * cells);
|
|
3511
3521
|
if (positions <= budget) return;
|
|
3512
3522
|
throw new XlvError("XLV_E_LIMIT_EXCEEDED", `${what} of sheet "${sheet}" would have ${String(positions)} positions (rows × columns) for ${String(cells)} cells, more than the limit of ${String(budget)}.`, {
|
|
3513
3523
|
location: { sheet },
|
|
3514
|
-
hint: `The cells are spread over a far larger area than they fill. Pass a smaller range (for example { range: "A1:H1000" } or --range), or raise limits.maxCells if the file is trusted.`,
|
|
3524
|
+
hint: hint ?? `The cells are spread over a far larger area than they fill. Pass a smaller range (for example { range: "A1:H1000" } or --range), or raise limits.maxCells if the file is trusted.`,
|
|
3515
3525
|
details: {
|
|
3516
3526
|
limit: "maxCells",
|
|
3517
3527
|
value: budget
|
|
3518
3528
|
}
|
|
3519
3529
|
});
|
|
3520
3530
|
}
|
|
3531
|
+
/**
|
|
3532
|
+
* Every row object repeats the keys of all columns, so a long header makes a small file give
|
|
3533
|
+
* gigabytes of keys (and JSON.stringify of the rows fail). The key text of all rows is bounded
|
|
3534
|
+
* by `limits.maxSharedStringChars`, the bound on text that cells repeat (D-H01).
|
|
3535
|
+
*/
|
|
3536
|
+
function checkKeyText(columns, rows, limit, sheet) {
|
|
3537
|
+
let perRow = 0;
|
|
3538
|
+
for (const c of columns) perRow += c.key.length;
|
|
3539
|
+
if (perRow * rows <= limit) return;
|
|
3540
|
+
throw new XlvError("XLV_E_LIMIT_EXCEEDED", `The rows of the table of sheet "${sheet}" would repeat ${String(perRow)} characters of column names in each of ${String(rows)} rows, more than the limit of ${String(limit)} characters.`, {
|
|
3541
|
+
location: { sheet },
|
|
3542
|
+
hint: `Each row object holds the text of every column name. Read fewer columns or rows with the range option (for example { range: "A1:H1000" } or --range), or raise limits.maxSharedStringChars if the file is trusted.`,
|
|
3543
|
+
details: {
|
|
3544
|
+
limit: "maxSharedStringChars",
|
|
3545
|
+
value: limit
|
|
3546
|
+
}
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3521
3549
|
// src/sml/numfmt.ts
|
|
3522
3550
|
/** A `Map` whose contents cannot change after construction. */
|
|
3523
3551
|
var FrozenMap = class extends Map {
|
|
@@ -4898,8 +4926,9 @@ function invalidName(name, message, hint) {
|
|
|
4898
4926
|
});
|
|
4899
4927
|
}
|
|
4900
4928
|
/**
|
|
4901
|
-
* Checks a defined name given by a caller. `sheets`
|
|
4902
|
-
* `null` when the sheets are not known yet (the sheet is then checked later by the
|
|
4929
|
+
* Checks a defined name given by a caller. `sheets` holds the workbook's sheet names in sheet
|
|
4930
|
+
* order, or is `null` when the sheets are not known yet (the sheet is then checked later by the
|
|
4931
|
+
* caller).
|
|
4903
4932
|
*/
|
|
4904
4933
|
function checkDefinedName(name, refersTo, options, sheets) {
|
|
4905
4934
|
if (typeof name !== "string") throw invalidName(String(name), "A defined name must be a string.", "For example workbook.defineName(\"Rates\", \"Sheet1!$A$1:$A$10\").");
|
|
@@ -4963,10 +4992,10 @@ function checkDefinedName(name, refersTo, options, sheets) {
|
|
|
4963
4992
|
}
|
|
4964
4993
|
/** Throws `XLV_E_SHEET_NOT_FOUND` if the sheet of a local name does not exist. */
|
|
4965
4994
|
function checkNameSheet(name, sheet, sheets) {
|
|
4966
|
-
if (sheets.
|
|
4995
|
+
if (sheets.has(sheet)) return;
|
|
4967
4996
|
throw new XlvError("XLV_E_SHEET_NOT_FOUND", `The defined name "${name}" belongs to the sheet "${sheet.slice(0, 100)}", which the workbook does not have.`, {
|
|
4968
4997
|
location: { sheet: sheet.slice(0, 100) },
|
|
4969
|
-
hint: `Add the sheet first, or use one of: ${sheets.
|
|
4998
|
+
hint: `Add the sheet first, or use one of: ${sheets.size > 0 ? [...sheets].map((s) => `"${s}"`).join(", ") : "(no sheets yet)"}.`,
|
|
4970
4999
|
details: {
|
|
4971
5000
|
name,
|
|
4972
5001
|
available: [...sheets]
|
|
@@ -4974,13 +5003,20 @@ function checkNameSheet(name, sheet, sheets) {
|
|
|
4974
5003
|
});
|
|
4975
5004
|
}
|
|
4976
5005
|
/**
|
|
5006
|
+
* The key of a defined name in its scope: names are not case-sensitive (P1 §18.17.2.5) and are
|
|
5007
|
+
* unique per sheet for local names and among workbook-level names otherwise ([MS-OI29500]
|
|
5008
|
+
* §2.1.568 n). A name has no NUL character, so the last one separates the scope from the name.
|
|
5009
|
+
*/
|
|
5010
|
+
function nameScopeKey(n) {
|
|
5011
|
+
return `${n.sheet === null ? "w" : `s${n.sheet}`}\u0000${n.name.toLowerCase()}`;
|
|
5012
|
+
}
|
|
5013
|
+
/**
|
|
4977
5014
|
* Throws `XLV_E_INVALID_DEFINED_NAME` if `added` has the same name and scope as one of
|
|
4978
|
-
* `existing
|
|
4979
|
-
*
|
|
5015
|
+
* `existing`, which maps {@link nameScopeKey} to the first name with that key (D-H01: a map,
|
|
5016
|
+
* not a search of every name for each one added).
|
|
4980
5017
|
*/
|
|
4981
5018
|
function checkUniqueName(added, existing) {
|
|
4982
|
-
const
|
|
4983
|
-
const clash = existing.find((n) => n.sheet === added.sheet && n.name.toLowerCase() === key);
|
|
5019
|
+
const clash = existing.get(nameScopeKey(added));
|
|
4984
5020
|
if (clash === void 0) return;
|
|
4985
5021
|
throw invalidName(added.name, `The ${added.sheet === null ? "workbook" : `sheet "${added.sheet}"`} already has a defined name "${clash.name}".`, "Names are not case-sensitive and must be unique in their scope. Choose another name, or make it local to a sheet with { sheet: \"…\" }.");
|
|
4986
5022
|
}
|
|
@@ -5547,13 +5583,8 @@ const CONTENT_TYPES_LOCATION = "/[Content_Types].xml";
|
|
|
5547
5583
|
/** Media type of Relationships parts (Part 2 §6.5.2.1, F-OPC-07). */
|
|
5548
5584
|
const RELATIONSHIPS_CONTENT_TYPE = "application/vnd.openxmlformats-package.relationships+xml";
|
|
5549
5585
|
const PACKAGE_RELATIONSHIPS = "/_rels/.rels";
|
|
5550
|
-
const MAX_SHOWN = 200;
|
|
5551
5586
|
const INVALID_HINT = "The package structure is damaged or was written by a program that does not follow ECMA-376 Part 2. Re-save the file in a spreadsheet application; if a program generated it, report the problem to its authors.";
|
|
5552
5587
|
const EMPTY = Object.freeze([]);
|
|
5553
|
-
/** Shortens a value from the file for messages and details. */
|
|
5554
|
-
function clip(value) {
|
|
5555
|
-
return value.length > MAX_SHOWN ? `${value.slice(0, MAX_SHOWN)}…` : value;
|
|
5556
|
-
}
|
|
5557
5588
|
function invalid$1(part, message, reason, extra = {}) {
|
|
5558
5589
|
return new XlvError("XLV_E_PART_INVALID", message, {
|
|
5559
5590
|
location: { part },
|
|
@@ -5928,7 +5959,7 @@ function parseWorkbookPart(xml, ctx) {
|
|
|
5928
5959
|
const rawDate1904 = childNamed(root, ns, "workbookPr")?.attrs.get("date1904");
|
|
5929
5960
|
if (rawDate1904 !== void 0) {
|
|
5930
5961
|
const parsed = parseXsdBoolean(rawDate1904);
|
|
5931
|
-
if (parsed === null) ctx.warnings.add("XLV_W_INVALID_VALUE", `workbookPr@date1904 has the invalid value "${rawDate1904}"; using the 1900 date system.`, { part: ctx.part });
|
|
5962
|
+
if (parsed === null) ctx.warnings.add("XLV_W_INVALID_VALUE", `workbookPr@date1904 has the invalid value "${clip(rawDate1904)}"; using the 1900 date system.`, { part: ctx.part });
|
|
5932
5963
|
else date1904 = parsed;
|
|
5933
5964
|
}
|
|
5934
5965
|
const sheetsElement = childNamed(root, ns, "sheets");
|
|
@@ -5942,7 +5973,7 @@ function parseWorkbookPart(xml, ctx) {
|
|
|
5942
5973
|
const rawState = el.attrs.get("state") ?? "visible";
|
|
5943
5974
|
let state = SHEET_STATES.find((s) => s === rawState);
|
|
5944
5975
|
if (state === void 0) {
|
|
5945
|
-
ctx.warnings.add("XLV_W_INVALID_VALUE", `Sheet "${name}" has the unknown state "${rawState}"; treating it as visible.`, {
|
|
5976
|
+
ctx.warnings.add("XLV_W_INVALID_VALUE", `Sheet "${name}" has the unknown state "${clip(rawState)}"; treating it as visible.`, {
|
|
5946
5977
|
part: ctx.part,
|
|
5947
5978
|
sheet: name
|
|
5948
5979
|
});
|
|
@@ -9102,6 +9133,18 @@ function createWorksheetParser(ctx, onRow) {
|
|
|
9102
9133
|
let drawingRelId = null;
|
|
9103
9134
|
let pictureRelId = null;
|
|
9104
9135
|
const print = createPrintCollector((message) => ctx.warnings.add("XLV_W_INVALID_VALUE", message, loc()));
|
|
9136
|
+
let buffered = 0;
|
|
9137
|
+
const bufferCell = (ref) => {
|
|
9138
|
+
if (!onRow || ++buffered <= 16384) return;
|
|
9139
|
+
throw new XlvError("XLV_E_LIMIT_EXCEEDED", `One row element of the sheet holds more than ${String(MAX_COLUMNS)} cells, more than a row can have.`, {
|
|
9140
|
+
location: loc(ref),
|
|
9141
|
+
hint: `A row has at most ${String(MAX_COLUMNS)} cells; the cells of this row element name other rows, which the streaming reader would have to hold in memory. The file is damaged or was made to exhaust memory. Re-save it in a spreadsheet application, or read it with readWorkbook, which bounds all cells with limits.maxCells.`,
|
|
9142
|
+
details: {
|
|
9143
|
+
limit: "maxCells",
|
|
9144
|
+
value: MAX_COLUMNS
|
|
9145
|
+
}
|
|
9146
|
+
});
|
|
9147
|
+
};
|
|
9105
9148
|
const rules = createRuleCollector({
|
|
9106
9149
|
ns: () => ns ?? SML_NS.transitional,
|
|
9107
9150
|
maxElements: ctx.limits.maxXmlTreeElements,
|
|
@@ -9338,6 +9381,7 @@ function createWorksheetParser(ctx, onRow) {
|
|
|
9338
9381
|
valued.delete(p.col);
|
|
9339
9382
|
cellCount--;
|
|
9340
9383
|
ctx.budget.remaining++;
|
|
9384
|
+
buffered--;
|
|
9341
9385
|
ctx.warnings.add("XLV_W_DUPLICATE_CELL", "The sheet defines the same cell more than once; the last definition is used.", loc(p.ref));
|
|
9342
9386
|
}
|
|
9343
9387
|
let styled = styleOnly.get(p.row);
|
|
@@ -9359,6 +9403,7 @@ function createWorksheetParser(ctx, onRow) {
|
|
|
9359
9403
|
value: ctx.limits.maxCells
|
|
9360
9404
|
}
|
|
9361
9405
|
});
|
|
9406
|
+
bufferCell(p.ref);
|
|
9362
9407
|
styleOnlyIndex.get(p.row)?.set(p.col, styled.length);
|
|
9363
9408
|
styled.push(p.col, p.styleIndex);
|
|
9364
9409
|
if (p.col > (styleOnlyMax.get(p.row) ?? 0)) styleOnlyMax.set(p.row, p.col);
|
|
@@ -9370,6 +9415,7 @@ function createWorksheetParser(ctx, onRow) {
|
|
|
9370
9415
|
if (list !== void 0) list[at + 1] = -1;
|
|
9371
9416
|
styleOnlyIndex.get(p.row)?.delete(p.col);
|
|
9372
9417
|
ctx.budget.remaining++;
|
|
9418
|
+
buffered--;
|
|
9373
9419
|
ctx.warnings.add("XLV_W_DUPLICATE_CELL", "The sheet defines the same cell more than once; the last definition is used.", loc(p.ref));
|
|
9374
9420
|
}
|
|
9375
9421
|
let rowCells = rows.get(p.row);
|
|
@@ -9388,6 +9434,7 @@ function createWorksheetParser(ctx, onRow) {
|
|
|
9388
9434
|
}
|
|
9389
9435
|
});
|
|
9390
9436
|
cellCount++;
|
|
9437
|
+
bufferCell(p.ref);
|
|
9391
9438
|
}
|
|
9392
9439
|
rowCells.set(p.col, {
|
|
9393
9440
|
ref: p.ref,
|
|
@@ -9418,6 +9465,7 @@ function createWorksheetParser(ctx, onRow) {
|
|
|
9418
9465
|
/** Streaming: expands the pending shared formulas and hands the buffered cells on. */
|
|
9419
9466
|
const flushRows = () => {
|
|
9420
9467
|
if (!onRow) return;
|
|
9468
|
+
buffered = 0;
|
|
9421
9469
|
const info = rowInfo.get(currentRow);
|
|
9422
9470
|
rowInfo.delete(currentRow);
|
|
9423
9471
|
if (rows.size === 0 && styleOnly.size === 0) return;
|
|
@@ -10826,6 +10874,12 @@ function readNotes(pkg, part, sheet, limits, warnings, foreign = NO_PARTS) {
|
|
|
10826
10874
|
});
|
|
10827
10875
|
if (notes.length === 0) return notes;
|
|
10828
10876
|
const seen = /* @__PURE__ */ new Set();
|
|
10877
|
+
const byRef = /* @__PURE__ */ new Map();
|
|
10878
|
+
notes.forEach((note, i) => {
|
|
10879
|
+
const list = byRef.get(note.ref);
|
|
10880
|
+
if (list === void 0) byRef.set(note.ref, [i]);
|
|
10881
|
+
else list.push(i);
|
|
10882
|
+
});
|
|
10829
10883
|
for (const vml of rels) {
|
|
10830
10884
|
if (vml.external || classifyRelationship(vml.type)?.name !== "vmlDrawing") continue;
|
|
10831
10885
|
const key = partKey(vml.target);
|
|
@@ -10840,13 +10894,10 @@ function readNotes(pkg, part, sheet, limits, warnings, foreign = NO_PARTS) {
|
|
|
10840
10894
|
warnings
|
|
10841
10895
|
});
|
|
10842
10896
|
if (boxes.size === 0) continue;
|
|
10843
|
-
for (
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
box
|
|
10848
|
-
});
|
|
10849
|
-
}
|
|
10897
|
+
for (const [ref, box] of boxes) for (const i of byRef.get(ref) ?? []) if (notes[i].box === void 0) notes[i] = Object.freeze({
|
|
10898
|
+
...notes[i],
|
|
10899
|
+
box
|
|
10900
|
+
});
|
|
10850
10901
|
}
|
|
10851
10902
|
return notes;
|
|
10852
10903
|
}
|
|
@@ -11421,7 +11472,8 @@ function detectHeader(rows, width, scanRows) {
|
|
|
11421
11472
|
const next = nonEmpty[i + 1];
|
|
11422
11473
|
if (years.length > 0) {
|
|
11423
11474
|
const consecutive = years.every((cell, k) => k === 0 || Math.abs(cell.value - years[k - 1].value) === 1);
|
|
11424
|
-
const
|
|
11475
|
+
const yearCols = new Set(years.map((y) => y.col));
|
|
11476
|
+
const below = next?.filter((cell) => yearCols.has(cell.col)) ?? [];
|
|
11425
11477
|
if (!consecutive || !below.some((cell) => cell.value !== null && !isYear(cell.value))) continue;
|
|
11426
11478
|
}
|
|
11427
11479
|
const distinct = new Set(texts.map((cell) => cell.value.trim().toLowerCase())).size === texts.length;
|
|
@@ -11477,6 +11529,103 @@ function checkMergedValues(sheet, rows, warnings) {
|
|
|
11477
11529
|
if (work > MAX_MERGE_CHECK_WORK) return;
|
|
11478
11530
|
}
|
|
11479
11531
|
}
|
|
11532
|
+
// src/util/range-index.ts
|
|
11533
|
+
/** Block heights of the row levels: 4, 16, …, 4^10 = 1 048 576 rows (the whole sheet). */
|
|
11534
|
+
const ROW_SIZES = [
|
|
11535
|
+
4,
|
|
11536
|
+
16,
|
|
11537
|
+
64,
|
|
11538
|
+
256,
|
|
11539
|
+
1024,
|
|
11540
|
+
4096,
|
|
11541
|
+
16384,
|
|
11542
|
+
65536,
|
|
11543
|
+
262144,
|
|
11544
|
+
1048576
|
|
11545
|
+
];
|
|
11546
|
+
/** Block widths of the column levels: 4, 16, …, 4^7 = 16 384 columns (the whole sheet). */
|
|
11547
|
+
const COL_SIZES = [
|
|
11548
|
+
4,
|
|
11549
|
+
16,
|
|
11550
|
+
64,
|
|
11551
|
+
256,
|
|
11552
|
+
1024,
|
|
11553
|
+
4096,
|
|
11554
|
+
16384
|
|
11555
|
+
];
|
|
11556
|
+
function levelOf(sizes, length) {
|
|
11557
|
+
for (let i = 0; i < sizes.length; i++) if (length <= sizes[i]) return i;
|
|
11558
|
+
return sizes.length - 1;
|
|
11559
|
+
}
|
|
11560
|
+
function blockOf(index, size) {
|
|
11561
|
+
return Math.floor((index - 1) / size);
|
|
11562
|
+
}
|
|
11563
|
+
function overlap(a, b) {
|
|
11564
|
+
return a.start.row <= b.end.row && b.start.row <= a.end.row && a.start.col <= b.end.col && b.start.col <= a.end.col;
|
|
11565
|
+
}
|
|
11566
|
+
/**
|
|
11567
|
+
* A grid of blocks at several sizes. A range is stored in the level whose block height is the
|
|
11568
|
+
* smallest at least as tall as the range and whose block width is the smallest at least as wide,
|
|
11569
|
+
* in every block of that level it touches (at most 2 × 2), so a huge range costs no more than a
|
|
11570
|
+
* small one. A query visits, in each level that holds ranges, the blocks it touches that hold
|
|
11571
|
+
* ranges (the smaller of the blocks it spans and the blocks the level holds). Ranges of a level
|
|
11572
|
+
* are at least about a quarter of its block size in each dimension, so when the stored ranges do
|
|
11573
|
+
* not overlap each other (merged ranges, tables), each block holds a bounded number of them.
|
|
11574
|
+
*/
|
|
11575
|
+
var RangeIndex = class {
|
|
11576
|
+
/** Level (row level × 16 + column level) → column block → row block → ranges. */
|
|
11577
|
+
levels = /* @__PURE__ */ new Map();
|
|
11578
|
+
/** Adds a range. */
|
|
11579
|
+
add(range) {
|
|
11580
|
+
const a = levelOf(ROW_SIZES, range.end.row - range.start.row + 1);
|
|
11581
|
+
const b = levelOf(COL_SIZES, range.end.col - range.start.col + 1);
|
|
11582
|
+
const rows = ROW_SIZES[a];
|
|
11583
|
+
const cols = COL_SIZES[b];
|
|
11584
|
+
let level = this.levels.get(a * 16 + b);
|
|
11585
|
+
if (level === void 0) {
|
|
11586
|
+
level = /* @__PURE__ */ new Map();
|
|
11587
|
+
this.levels.set(a * 16 + b, level);
|
|
11588
|
+
}
|
|
11589
|
+
for (let cb = blockOf(range.start.col, cols); cb <= blockOf(range.end.col, cols); cb++) {
|
|
11590
|
+
let column = level.get(cb);
|
|
11591
|
+
if (column === void 0) {
|
|
11592
|
+
column = /* @__PURE__ */ new Map();
|
|
11593
|
+
level.set(cb, column);
|
|
11594
|
+
}
|
|
11595
|
+
for (let rb = blockOf(range.start.row, rows); rb <= blockOf(range.end.row, rows); rb++) {
|
|
11596
|
+
const list = column.get(rb);
|
|
11597
|
+
if (list === void 0) column.set(rb, [range]);
|
|
11598
|
+
else list.push(range);
|
|
11599
|
+
}
|
|
11600
|
+
}
|
|
11601
|
+
}
|
|
11602
|
+
/** Whether `range` shares a cell with a range added before. */
|
|
11603
|
+
overlaps(range) {
|
|
11604
|
+
const hit = (list) => list !== void 0 && list.some((r) => overlap(r, range));
|
|
11605
|
+
for (const [key, level] of this.levels) {
|
|
11606
|
+
const rows = ROW_SIZES[key >> 4];
|
|
11607
|
+
const cols = COL_SIZES[key & 15];
|
|
11608
|
+
const rb1 = blockOf(range.start.row, rows);
|
|
11609
|
+
const rb2 = blockOf(range.end.row, rows);
|
|
11610
|
+
const cb1 = blockOf(range.start.col, cols);
|
|
11611
|
+
const cb2 = blockOf(range.end.col, cols);
|
|
11612
|
+
const inColumn = (column) => {
|
|
11613
|
+
if (rb2 - rb1 + 1 <= column.size) {
|
|
11614
|
+
for (let rb = rb1; rb <= rb2; rb++) if (hit(column.get(rb))) return true;
|
|
11615
|
+
return false;
|
|
11616
|
+
}
|
|
11617
|
+
for (const [rb, list] of column) if (rb >= rb1 && rb <= rb2 && hit(list)) return true;
|
|
11618
|
+
return false;
|
|
11619
|
+
};
|
|
11620
|
+
if (cb2 - cb1 + 1 <= level.size) for (let cb = cb1; cb <= cb2; cb++) {
|
|
11621
|
+
const column = level.get(cb);
|
|
11622
|
+
if (column !== void 0 && inColumn(column)) return true;
|
|
11623
|
+
}
|
|
11624
|
+
else for (const [cb, column] of level) if (cb >= cb1 && cb <= cb2 && inColumn(column)) return true;
|
|
11625
|
+
}
|
|
11626
|
+
return false;
|
|
11627
|
+
}
|
|
11628
|
+
};
|
|
11480
11629
|
// src/write.ts
|
|
11481
11630
|
/**
|
|
11482
11631
|
* Error values accepted in cells (ECMA-376 Part 1 §18.17.3, fact E-33).
|
|
@@ -11692,9 +11841,13 @@ var NewSheet = class {
|
|
|
11692
11841
|
priorities = /* @__PURE__ */ new Set();
|
|
11693
11842
|
/** @internal Protection, page setup, headers and footers, page breaks, print area and titles. */
|
|
11694
11843
|
print = EMPTY_PRINT_DATA;
|
|
11695
|
-
/** @internal Tables in the order they were added (TD-01). */
|
|
11844
|
+
/** @internal Tables in the order they were added (TD-01); add them with {@link NewSheet.pushTable}. */
|
|
11696
11845
|
tableList = [];
|
|
11697
11846
|
count = 0;
|
|
11847
|
+
mergeIndex = new RangeIndex();
|
|
11848
|
+
mergesIndexed = 0;
|
|
11849
|
+
tableIndex = new RangeIndex();
|
|
11850
|
+
tablesIndexed = 0;
|
|
11698
11851
|
/** @internal Use {@link NewWorkbook.addSheet}. */
|
|
11699
11852
|
constructor(name, state, book, sheetId = null) {
|
|
11700
11853
|
this.name = name;
|
|
@@ -11974,25 +12127,44 @@ var NewSheet = class {
|
|
|
11974
12127
|
merge(range) {
|
|
11975
12128
|
const r = this.range(range);
|
|
11976
12129
|
if (r.start.row === r.end.row && r.start.col === r.end.col) throw invalidRef(range, this.name, "range of two or more cells", "\"A1:C1\"");
|
|
11977
|
-
|
|
11978
|
-
const
|
|
11979
|
-
|
|
12130
|
+
if (this.overlapsMerge(r)) {
|
|
12131
|
+
for (const m of this.mergeList) if (r.start.row <= m.end.row && m.start.row <= r.end.row && r.start.col <= m.end.col && m.start.col <= r.end.col) {
|
|
12132
|
+
const other = `${formatCellRef(m.start.row, m.start.col)}:${formatCellRef(m.end.row, m.end.col)}`;
|
|
12133
|
+
throw new XlvError("XLV_E_OVERLAPPING_MERGE", `The range ${range} overlaps the merged range ${other}.`, {
|
|
12134
|
+
location: { sheet: this.name },
|
|
12135
|
+
hint: "Merged ranges must not overlap. Choose ranges that share no cells.",
|
|
12136
|
+
details: {
|
|
12137
|
+
range: String(range),
|
|
12138
|
+
other
|
|
12139
|
+
}
|
|
12140
|
+
});
|
|
12141
|
+
}
|
|
12142
|
+
}
|
|
12143
|
+
if (this.overlapsTable(r)) {
|
|
12144
|
+
for (const t of this.tableList) if (r.start.row <= t.range.end.row && t.range.start.row <= r.end.row && r.start.col <= t.range.end.col && t.range.start.col <= r.end.col) throw new XlvError("XLV_E_INVALID_TABLE", `The range ${range} overlaps table "${t.name}".`, {
|
|
11980
12145
|
location: { sheet: this.name },
|
|
11981
|
-
hint: "
|
|
11982
|
-
details: {
|
|
11983
|
-
range: String(range),
|
|
11984
|
-
other
|
|
11985
|
-
}
|
|
12146
|
+
hint: "Tables cannot contain merged cells; merge cells outside the table.",
|
|
12147
|
+
details: { name: t.name }
|
|
11986
12148
|
});
|
|
11987
12149
|
}
|
|
11988
|
-
for (const t of this.tableList) if (r.start.row <= t.range.end.row && t.range.start.row <= r.end.row && r.start.col <= t.range.end.col && t.range.start.col <= r.end.col) throw new XlvError("XLV_E_INVALID_TABLE", `The range ${range} overlaps table "${t.name}".`, {
|
|
11989
|
-
location: { sheet: this.name },
|
|
11990
|
-
hint: "Tables cannot contain merged cells; merge cells outside the table.",
|
|
11991
|
-
details: { name: t.name }
|
|
11992
|
-
});
|
|
11993
12150
|
this.mergeList.push(r);
|
|
11994
12151
|
return this;
|
|
11995
12152
|
}
|
|
12153
|
+
/** @internal Whether `range` shares a cell with a merged range of the sheet. */
|
|
12154
|
+
overlapsMerge(range) {
|
|
12155
|
+
while (this.mergesIndexed < this.mergeList.length) this.mergeIndex.add(this.mergeList[this.mergesIndexed++]);
|
|
12156
|
+
return this.mergeIndex.overlaps(range);
|
|
12157
|
+
}
|
|
12158
|
+
/** @internal Whether `range` shares a cell with a table of the sheet. */
|
|
12159
|
+
overlapsTable(range) {
|
|
12160
|
+
while (this.tablesIndexed < this.tableList.length) this.tableIndex.add(this.tableList[this.tablesIndexed++].range);
|
|
12161
|
+
return this.tableIndex.overlaps(range);
|
|
12162
|
+
}
|
|
12163
|
+
/** @internal Adds a table checked by {@link NewWorkbook.checkTable} to the sheet. */
|
|
12164
|
+
pushTable(t) {
|
|
12165
|
+
this.tableList.push(t);
|
|
12166
|
+
this.book.tableAdded(t);
|
|
12167
|
+
}
|
|
11996
12168
|
/**
|
|
11997
12169
|
* Freezes the top rows and left columns so that they stay visible when scrolling. Pass
|
|
11998
12170
|
* `{ rows: 0, cols: 0 }` to remove the frozen panes.
|
|
@@ -12473,7 +12645,7 @@ var NewSheet = class {
|
|
|
12473
12645
|
}
|
|
12474
12646
|
} else if (c.totalsRowLabel !== null) this.set(ref, c.totalsRowLabel);
|
|
12475
12647
|
});
|
|
12476
|
-
this.
|
|
12648
|
+
this.pushTable(t);
|
|
12477
12649
|
return this;
|
|
12478
12650
|
}
|
|
12479
12651
|
/**
|
|
@@ -12868,6 +13040,18 @@ var NewWorkbook = class {
|
|
|
12868
13040
|
properties;
|
|
12869
13041
|
list = [];
|
|
12870
13042
|
names = [];
|
|
13043
|
+
/** Sheets by lower-case name. */
|
|
13044
|
+
sheetsByName = /* @__PURE__ */ new Map();
|
|
13045
|
+
/** Sheet names as given, in sheet order. */
|
|
13046
|
+
sheetNames = /* @__PURE__ */ new Set();
|
|
13047
|
+
/** Sheets by the sheetId given to addSheet. */
|
|
13048
|
+
sheetsById = /* @__PURE__ */ new Map();
|
|
13049
|
+
/** Defined names by nameScopeKey. */
|
|
13050
|
+
namesByScope = /* @__PURE__ */ new Map();
|
|
13051
|
+
/** Lower-case defined names, of any scope. */
|
|
13052
|
+
lowerNames = /* @__PURE__ */ new Set();
|
|
13053
|
+
/** Lower-case names and display names of the tables of all sheets. */
|
|
13054
|
+
tableNames = /* @__PURE__ */ new Set();
|
|
12871
13055
|
styleIds = /* @__PURE__ */ new Map();
|
|
12872
13056
|
styleList = [{}];
|
|
12873
13057
|
/** @internal `workbookProtection`, set by {@link NewWorkbook.protect}. */
|
|
@@ -13003,7 +13187,7 @@ var NewWorkbook = class {
|
|
|
13003
13187
|
if (state !== "visible" && state !== "hidden" && state !== "veryHidden") throw invalidOption(`Sheet state ${JSON.stringify(String(state).slice(0, 40))} is not valid.`, "Use \"visible\", \"hidden\" or \"veryHidden\".");
|
|
13004
13188
|
const checked = checkSheetName(name);
|
|
13005
13189
|
const lower = checked.toLowerCase();
|
|
13006
|
-
const existing = this.
|
|
13190
|
+
const existing = this.sheetsByName.get(lower);
|
|
13007
13191
|
if (existing) throw new XlvError("XLV_E_DUPLICATE_SHEET_NAME", `The workbook already has a sheet named "${existing.name}".`, {
|
|
13008
13192
|
hint: "Sheet names must be unique, ignoring upper and lower case. Choose another name.",
|
|
13009
13193
|
details: {
|
|
@@ -13015,7 +13199,7 @@ var NewWorkbook = class {
|
|
|
13015
13199
|
if (o.has("sheetId")) {
|
|
13016
13200
|
const id = o.get("sheetId");
|
|
13017
13201
|
if (typeof id !== "number" || !Number.isInteger(id) || id < 1 || id > MAX_SHEET_ID) throw invalidOption(`The sheet option "sheetId" must be a whole number from 1 to ${String(MAX_SHEET_ID)}.`, "For example { sheetId: 3 }, or leave it out to take the lowest unused id.", { name: checked });
|
|
13018
|
-
const owner = this.
|
|
13202
|
+
const owner = this.sheetsById.get(id);
|
|
13019
13203
|
if (owner) throw invalidOption(`Sheet "${owner.name}" already has the sheetId ${String(id)}.`, "Sheet ids must be unique in the workbook. Give another id, or leave it out to take the lowest unused id.", {
|
|
13020
13204
|
name: checked,
|
|
13021
13205
|
existing: owner.name
|
|
@@ -13024,6 +13208,9 @@ var NewWorkbook = class {
|
|
|
13024
13208
|
}
|
|
13025
13209
|
const sheet = new NewSheet(checked, state, this, sheetId);
|
|
13026
13210
|
this.list.push(sheet);
|
|
13211
|
+
this.sheetsByName.set(lower, sheet);
|
|
13212
|
+
this.sheetNames.add(checked);
|
|
13213
|
+
if (sheetId !== null) this.sheetsById.set(sheetId, sheet);
|
|
13027
13214
|
return sheet;
|
|
13028
13215
|
}
|
|
13029
13216
|
/**
|
|
@@ -13061,15 +13248,22 @@ var NewWorkbook = class {
|
|
|
13061
13248
|
* ```
|
|
13062
13249
|
*/
|
|
13063
13250
|
defineName(name, refersTo, options) {
|
|
13064
|
-
const n = checkDefinedName(name, refersTo, options, this.
|
|
13065
|
-
checkUniqueName(n, this.
|
|
13066
|
-
if (this.
|
|
13251
|
+
const n = checkDefinedName(name, refersTo, options, this.sheetNames);
|
|
13252
|
+
checkUniqueName(n, this.namesByScope);
|
|
13253
|
+
if (this.tableNames.has(n.name.toLowerCase())) throw new XlvError("XLV_E_INVALID_DEFINED_NAME", `"${n.name}" is already the name of a table.`, {
|
|
13067
13254
|
hint: "Table names and defined names share one list in a workbook (ECMA-376 Part 1 §18.5.1.2); choose another name.",
|
|
13068
13255
|
details: { name: n.name }
|
|
13069
13256
|
});
|
|
13070
13257
|
this.names.push(n);
|
|
13258
|
+
this.namesByScope.set(nameScopeKey(n), n);
|
|
13259
|
+
this.lowerNames.add(n.name.toLowerCase());
|
|
13071
13260
|
return this;
|
|
13072
13261
|
}
|
|
13262
|
+
/** @internal Records the names of a table added to a sheet ({@link NewSheet.pushTable}). */
|
|
13263
|
+
tableAdded(t) {
|
|
13264
|
+
this.tableNames.add(t.name.toLowerCase());
|
|
13265
|
+
this.tableNames.add(t.displayName.toLowerCase());
|
|
13266
|
+
}
|
|
13073
13267
|
/**
|
|
13074
13268
|
* @internal Checks that a table's names are unique in the workbook (tables and defined names,
|
|
13075
13269
|
* ignoring case; Part 1 §18.5.1.2) and that it overlaps no table or merged range of its sheet.
|
|
@@ -13077,32 +13271,41 @@ var NewWorkbook = class {
|
|
|
13077
13271
|
checkTable(t, sheet) {
|
|
13078
13272
|
const where = { sheet: sheet.name };
|
|
13079
13273
|
const names = /* @__PURE__ */ new Set([t.name.toLowerCase(), t.displayName.toLowerCase()]);
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13274
|
+
const clashes = (known) => [...names].some((name) => known.has(name));
|
|
13275
|
+
if (clashes(this.tableNames)) {
|
|
13276
|
+
for (const other of this.list.flatMap((s) => s.tableList)) if (names.has(other.name.toLowerCase()) || names.has(other.displayName.toLowerCase())) throw new XlvError("XLV_E_INVALID_TABLE", `The workbook already has a table named "${other.displayName}".`, {
|
|
13277
|
+
location: where,
|
|
13278
|
+
hint: "Table names are unique in a workbook, ignoring case; choose another name, for example \"Sales_2\".",
|
|
13279
|
+
details: { name: t.name }
|
|
13280
|
+
});
|
|
13281
|
+
}
|
|
13282
|
+
if (clashes(this.lowerNames)) {
|
|
13283
|
+
for (const n of this.names) if (names.has(n.name.toLowerCase())) throw new XlvError("XLV_E_INVALID_TABLE", `"${n.name}" is already a defined name of the workbook.`, {
|
|
13284
|
+
location: where,
|
|
13285
|
+
hint: "Table names and defined names share one list in a workbook (ECMA-376 Part 1 §18.5.1.2); choose another table name.",
|
|
13286
|
+
details: { name: t.name }
|
|
13287
|
+
});
|
|
13288
|
+
}
|
|
13090
13289
|
const ref = (r) => `${formatCellRef(r.start.row, r.start.col)}:${formatCellRef(r.end.row, r.end.col)}`;
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13290
|
+
if (sheet.overlapsTable(t.range)) {
|
|
13291
|
+
for (const other of sheet.tableList) if (rangesOverlap(t.range, other.range)) throw new XlvError("XLV_E_INVALID_TABLE", `Table "${t.name}" (${ref(t.range)}) overlaps table "${other.name}" (${ref(other.range)}).`, {
|
|
13292
|
+
location: where,
|
|
13293
|
+
hint: "Tables must not share cells ([MS-OI29500] §2.1.680 h); move one of them.",
|
|
13294
|
+
details: { name: t.name }
|
|
13295
|
+
});
|
|
13296
|
+
}
|
|
13096
13297
|
if (sheet.filter !== null && rangesOverlap(t.range, sheet.filter)) throw new XlvError("XLV_E_INVALID_TABLE", `Table "${t.name}" (${ref(t.range)}) overlaps the sheet filter ${ref(sheet.filter)}.`, {
|
|
13097
13298
|
location: where,
|
|
13098
13299
|
hint: "A table has its own filter, and the sheet filter must not share cells with a table ([MS-OI29500] §2.1.680 h); remove the sheet filter (autoFilter(null)) or move the table.",
|
|
13099
13300
|
details: { name: t.name }
|
|
13100
13301
|
});
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13302
|
+
if (sheet.overlapsMerge(t.range)) {
|
|
13303
|
+
for (const m of sheet.mergeList) if (rangesOverlap(t.range, m)) throw new XlvError("XLV_E_INVALID_TABLE", `Table "${t.name}" (${ref(t.range)}) overlaps the merged range ${ref(m)}.`, {
|
|
13304
|
+
location: where,
|
|
13305
|
+
hint: "Tables cannot contain merged cells; unmerge the range or move the table.",
|
|
13306
|
+
details: { name: t.name }
|
|
13307
|
+
});
|
|
13308
|
+
}
|
|
13106
13309
|
}
|
|
13107
13310
|
/** Defined names in the order they were added. */
|
|
13108
13311
|
get definedNames() {
|
|
@@ -13317,4 +13520,4 @@ function openInput(input, limits, onWarning) {
|
|
|
13317
13520
|
if (onWarning !== void 0) for (const warning of workbook.warnings) onWarning(warning);
|
|
13318
13521
|
return workbook;
|
|
13319
13522
|
}
|
|
13320
|
-
export { fillXml as $,
|
|
13523
|
+
export { fillXml as $, drawingParts as $t, limitExceeded as A, WARNING_CODES as An, nameScopeKey as At, tokenize as B, checkKeyText as Bt, Bytes as C, normalizeLineEnds as Cn, relationshipsPartName as Ct, TAIL_BYTES as D, escapeAttr as Dn, normalizeTable as Dt, NotLoaded as E, encodeXstring as En, TABLE_CONTENT_TYPE as Et, validateLocalHeaders as F, mergeStyles as Ft, DEFAULT_FONT as G, printXml as Gt, X14_DV_URI as H, resolveLimits as Ht, WarningCollector as I, normalizeStyle as It, borderXml as J, legacyPasswordHash as Jt, SheetRels as K, sheetProtectionXml as Kt, sheetNotFound as L, stableKey as Lt, openZip as M, isXlvError as Mn, parseStyles as Mt, readCentralDirectory as N, READING_ORDERS as Nt, checkSource as O, escapeText as On, checkDefinedName as Ot, unsupported as P, checkStyleCount as Pt, createSharedStrings as Q, MediaParts as Qt, createWorksheetParser as R, isDateFormat as Rt, workbookPartsToLoad as S, decodeXml as Sn, openPackage as St, HEAD_BYTES as T, clip as Tn, zipNameToPartName as Tt, X14_NS as U, suggest as Ut, X14_CF_URI as V, checkOutputPositions as Vt, XM_NS as W, printNames as Wt, cellXml as X, DRAWING_CONTENT_TYPE as Xt, buildStyles as Y, verifyPassword as Yt, contentTypesXml as Z, IMAGE_CONTENT_TYPES as Zt, isoDate as _, corePropertiesXml as _n, FormulaGroups as _t, DATE_FORMAT as a, classifyRelationship as an, reusableBuiltinNumFmt as at, readWorkbook as b, parseXmlTree as bn, parseIsoDateTime as bt, NewSheet as c, MAX_ROWS as cn, sheetHead as ct, convertCellInput as d, formatRange as dn, sheetTail as dt, imageFormat as en, fontXml as et, createWorkbook as f, parseCellRef as fn, stringItemXml as ft, inspect as g, appPropertiesXml as gn, workbookXml as gt, detectHeader as h, CORE_PROPERTIES_PART as hn, workbookRelsXml as ht, readOptions as i, SML_NS as in, noteParts as it, locateCentralDirectory as j, XlvError as jn, EMPTY_STYLES as jt, corrupt as k, ERROR_CODES as kn, checkUniqueName as kt, NewWorkbook as l, columnLetters as ln, sheetRelsPartName as lt, writeWorkbook as m, APP_PROPERTIES_PART as mn, textElement as mt, oneOfOption as n, resolveImage as nn, hyperlinksXml as nt, DATE_TIME_FORMAT as o, relationshipTypes as on, rootRelsXml as ot, sortedNotes as p, parseRange as pn, tableParts as pt, attr as q, hashPassword as qt, openInput as r, REL_NS as rn, nameTitles as rt, ERROR_VALUES as s, MAX_COLUMNS as sn, rowStart as st, booleanOption as t, metricsOf as tn, formatNumber as tt, checkBookViews as u, formatCellRef as un, sheetRulesXml as ut, analyzeWorkbook as v, childNamed as vn, writeZip as vt, CORRUPT_HINT as w, parseXml as wn, partKey as wt, toBytes as x, createXmlByteStreamParser as xn, parseSharedStrings as xt, readNotes as y, childrenNamed as yn, crc32 as yt, shiftFormula as z, DEFAULT_READ_LIMITS as zt };
|