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.
@@ -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 = embed === void 0 ? void 0 : drawingRels.find((x) => x.id === embed && classifyRelationship(x.type)?.name === "image");
1920
- const linkId = nv === void 0 ? void 0 : childNamed(nv, a, "hlinkClick")?.attrs.get(`{${r}}id`);
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` lists the workbook's sheet names, or is
4902
- * `null` when the sheets are not known yet (the sheet is then checked later by the caller).
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.includes(sheet)) return;
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.length > 0 ? sheets.map((s) => `"${s}"`).join(", ") : "(no sheets yet)"}.`,
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`. Names are not case-sensitive (P1 §18.17.2.5) and are unique per sheet for local
4979
- * names and among workbook-level names otherwise ([MS-OI29500] §2.1.568 n).
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 key = added.name.toLowerCase();
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 (let i = 0; i < notes.length; i++) {
10844
- const box = boxes.get(notes[i].ref);
10845
- if (box !== void 0 && notes[i].box === void 0) notes[i] = Object.freeze({
10846
- ...notes[i],
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 below = next?.filter((cell) => years.some((y) => y.col === cell.col)) ?? [];
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
- 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) {
11978
- const other = `${formatCellRef(m.start.row, m.start.col)}:${formatCellRef(m.end.row, m.end.col)}`;
11979
- throw new XlvError("XLV_E_OVERLAPPING_MERGE", `The range ${range} overlaps the merged range ${other}.`, {
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: "Merged ranges must not overlap. Choose ranges that share no cells.",
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.tableList.push(t);
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.list.find((s) => s.name.toLowerCase() === lower);
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.list.find((s) => s.sheetId === id);
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.list.map((s) => s.name));
13065
- checkUniqueName(n, this.names);
13066
- if (this.list.flatMap((s) => s.tableList).find((t) => t.name.toLowerCase() === n.name.toLowerCase() || t.displayName.toLowerCase() === n.name.toLowerCase()) !== void 0) throw new XlvError("XLV_E_INVALID_DEFINED_NAME", `"${n.name}" is already the name of a table.`, {
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
- 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}".`, {
13081
- location: where,
13082
- hint: "Table names are unique in a workbook, ignoring case; choose another name, for example \"Sales_2\".",
13083
- details: { name: t.name }
13084
- });
13085
- 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.`, {
13086
- location: where,
13087
- hint: "Table names and defined names share one list in a workbook (ECMA-376 Part 1 §18.5.1.2); choose another table name.",
13088
- details: { name: t.name }
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
- 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)}).`, {
13092
- location: where,
13093
- hint: "Tables must not share cells ([MS-OI29500] §2.1.680 h); move one of them.",
13094
- details: { name: t.name }
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
- 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)}.`, {
13102
- location: where,
13103
- hint: "Tables cannot contain merged cells; unmerge the range or move the table.",
13104
- details: { name: t.name }
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 $, metricsOf as $t, limitExceeded as A, EMPTY_STYLES as At, tokenize as B, resolveLimits as Bt, Bytes as C, encodeXstring as Cn, relationshipsPartName as Ct, TAIL_BYTES as D, WARNING_CODES as Dn, normalizeTable as Dt, NotLoaded as E, ERROR_CODES as En, TABLE_CONTENT_TYPE as Et, validateLocalHeaders as F, normalizeStyle as Ft, DEFAULT_FONT as G, hashPassword as Gt, X14_DV_URI as H, printNames as Ht, WarningCollector as I, stableKey as It, borderXml as J, DRAWING_CONTENT_TYPE as Jt, SheetRels as K, legacyPasswordHash as Kt, sheetNotFound as L, isDateFormat as Lt, openZip as M, READING_ORDERS as Mt, readCentralDirectory as N, checkStyleCount as Nt, checkSource as O, XlvError as On, checkDefinedName as Ot, unsupported as P, mergeStyles as Pt, createSharedStrings as Q, imageFormat as Qt, createWorksheetParser as R, DEFAULT_READ_LIMITS as Rt, workbookPartsToLoad as S, parseXml as Sn, openPackage as St, HEAD_BYTES as T, escapeText as Tn, zipNameToPartName as Tt, X14_NS as U, printXml as Ut, X14_CF_URI as V, suggest as Vt, XM_NS as W, sheetProtectionXml as Wt, cellXml as X, MediaParts as Xt, buildStyles as Y, IMAGE_CONTENT_TYPES as Yt, contentTypesXml as Z, drawingParts as Zt, isoDate as _, childrenNamed as _n, FormulaGroups as _t, DATE_FORMAT as a, MAX_COLUMNS as an, reusableBuiltinNumFmt as at, readWorkbook as b, decodeXml as bn, parseIsoDateTime as bt, NewSheet as c, formatCellRef as cn, sheetHead as ct, convertCellInput as d, parseRange as dn, sheetTail as dt, resolveImage as en, fontXml as et, createWorkbook as f, APP_PROPERTIES_PART as fn, stringItemXml as ft, inspect as g, childNamed as gn, workbookXml as gt, detectHeader as h, corePropertiesXml as hn, workbookRelsXml as ht, readOptions as i, relationshipTypes as in, noteParts as it, locateCentralDirectory as j, parseStyles as jt, corrupt as k, isXlvError as kn, checkUniqueName as kt, NewWorkbook as l, formatRange as ln, sheetRelsPartName as lt, writeWorkbook as m, appPropertiesXml as mn, textElement as mt, oneOfOption as n, SML_NS as nn, hyperlinksXml as nt, DATE_TIME_FORMAT as o, MAX_ROWS as on, rootRelsXml as ot, sortedNotes as p, CORE_PROPERTIES_PART as pn, tableParts as pt, attr as q, verifyPassword as qt, openInput as r, classifyRelationship as rn, nameTitles as rt, ERROR_VALUES as s, columnLetters as sn, rowStart as st, booleanOption as t, REL_NS as tn, formatNumber as tt, checkBookViews as u, parseCellRef as un, sheetRulesXml as ut, analyzeWorkbook as v, parseXmlTree as vn, writeZip as vt, CORRUPT_HINT as w, escapeAttr as wn, partKey as wt, toBytes as x, normalizeLineEnds as xn, parseSharedStrings as xt, readNotes as y, createXmlByteStreamParser as yn, crc32 as yt, shiftFormula as z, checkOutputPositions as zt };
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 };