@cj-tech-master/excelts 9.5.8 → 9.5.9

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cj-tech-master/excelts v9.5.8
2
+ * @cj-tech-master/excelts v9.5.9
3
3
  * Zero-dependency TypeScript toolkit — Excel (XLSX), PDF, CSV, Markdown, XML, ZIP/TAR, and streaming.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the MIT License
@@ -9509,7 +9509,7 @@ var ExcelTS = (function(exports) {
9509
9509
  let l2;
9510
9510
  let l3;
9511
9511
  let n = 1;
9512
- if (level >= 4) throw new ColumnOutOfBoundsError(level, "Excel supports columns from 1 to 16384");
9512
+ if (level >= 4) throw new Error(`colCache._fill: invariant violated — level ${level} exceeds the 3-letter cap; callers must validate before invoking _fill`);
9513
9513
  if (this._l2nFill < 1 && level >= 1) {
9514
9514
  while (n <= 26) {
9515
9515
  c = this._dictionary[n - 1];
@@ -9548,7 +9548,10 @@ var ExcelTS = (function(exports) {
9548
9548
  }
9549
9549
  },
9550
9550
  l2n(l) {
9551
- if (!this._l2n[l]) this._fill(l.length);
9551
+ if (!this._l2n[l]) {
9552
+ if (l.length > 3) throw new ColumnOutOfBoundsError(l, "Excel supports columns from 1 to 16384");
9553
+ this._fill(l.length);
9554
+ }
9552
9555
  if (!this._l2n[l]) throw new ColumnOutOfBoundsError(l, `Invalid column letter: ${l}`);
9553
9556
  return this._l2n[l];
9554
9557
  },
@@ -10679,7 +10682,7 @@ var ExcelTS = (function(exports) {
10679
10682
  * @example quoteSheetName("It's Fine") // "'It''s Fine'"
10680
10683
  * @example quoteSheetName("2023 Data") // "'2023 Data'" (leading digit → quoted)
10681
10684
  */
10682
- function quoteSheetName(sheetName) {
10685
+ function quoteSheetName$1(sheetName) {
10683
10686
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(sheetName)) return `'${sheetName.replace(/'/g, "''")}'`;
10684
10687
  return sheetName;
10685
10688
  }
@@ -10720,7 +10723,7 @@ var ExcelTS = (function(exports) {
10720
10723
  }
10721
10724
  function buildPivotChartSourceName(pivotTable) {
10722
10725
  const tableName = pivotTable.name ?? `PivotTable${pivotTable.tableNumber}`;
10723
- return pivotTable.worksheetName ? `${quoteSheetName(pivotTable.worksheetName)}!${tableName}` : tableName;
10726
+ return pivotTable.worksheetName ? `${quoteSheetName$1(pivotTable.worksheetName)}!${tableName}` : tableName;
10724
10727
  }
10725
10728
  var DEFAULT_PIVOT_CHART_PIVOT_AREA_XML;
10726
10729
  var init_pivot_chart = __esmMin((() => {
@@ -21826,8 +21829,244 @@ onmessage = async (ev) => {
21826
21829
  }));
21827
21830
  //#endregion
21828
21831
  //#region src/modules/excel/xlsx/xform/book/workbook-xform.ts
21829
- var WorkbookXform;
21832
+ /**
21833
+ * Split a print-area string on its multi-range separators while honouring
21834
+ * single-quoted sheet name segments.
21835
+ *
21836
+ * Used by both the writer (parsing user-supplied `printArea` values) and
21837
+ * the reader (parsing the body of an OOXML `<definedName>` when the
21838
+ * defined-name layer hands us the raw text). Recognises both:
21839
+ * - `,` — the OOXML / Excel-native separator
21840
+ * - `&&` — the legacy excelts convention preserved on the public API
21841
+ *
21842
+ * Quoted sheet names (`'Q1, Forecast'!A1:B5`) are skipped over: any `,`,
21843
+ * `&`, or `'` inside a quoted name is preserved verbatim. A doubled
21844
+ * apostrophe inside a quoted segment (`''`) is the OOXML escape for a
21845
+ * literal apostrophe and does not terminate the quote.
21846
+ *
21847
+ * Empty / whitespace-only segments are dropped; the caller normalises
21848
+ * each surviving segment further with `parsePrintReference`.
21849
+ */
21850
+ function splitPrintAreaInput(input) {
21851
+ const result = [];
21852
+ let current = "";
21853
+ let inQuote = false;
21854
+ for (let i = 0; i < input.length; i++) {
21855
+ const ch = input[i];
21856
+ if (ch === "'") {
21857
+ if (inQuote && input[i + 1] === "'") {
21858
+ current += "''";
21859
+ i++;
21860
+ continue;
21861
+ }
21862
+ inQuote = !inQuote;
21863
+ current += ch;
21864
+ continue;
21865
+ }
21866
+ if (!inQuote) {
21867
+ if (ch === ",") {
21868
+ if (current.trim()) result.push(current);
21869
+ current = "";
21870
+ continue;
21871
+ }
21872
+ if (ch === "&" && input[i + 1] === "&") {
21873
+ if (current.trim()) result.push(current);
21874
+ current = "";
21875
+ i++;
21876
+ continue;
21877
+ }
21878
+ }
21879
+ current += ch;
21880
+ }
21881
+ if (current.trim()) result.push(current);
21882
+ return result;
21883
+ }
21884
+ /**
21885
+ * Quote a sheet name for inclusion in an OOXML defined-name reference.
21886
+ *
21887
+ * Per ECMA-376 §18.17 sheet names that contain spaces or any character
21888
+ * outside `[A-Za-z0-9_]` MUST be wrapped in single quotes; a literal
21889
+ * apostrophe inside the name is doubled. We always quote — over-quoting
21890
+ * is harmless (Excel parses both forms) and keeps the writer trivial.
21891
+ */
21892
+ function quoteSheetName(sheetName) {
21893
+ return `'${sheetName.replace(/'/g, "''")}'`;
21894
+ }
21895
+ /**
21896
+ * Find the index of the first `!` that lies outside any single-quoted
21897
+ * sheet-name segment, or `-1` if no unquoted `!` is present.
21898
+ *
21899
+ * Sheet names quoted with `'` may contain unbalanced characters, so we
21900
+ * walk the string honouring quote toggles before declaring a `!` to be
21901
+ * the sheet/address separator. Doubled apostrophes (`''`) inside a
21902
+ * quoted name are treated as a literal apostrophe per OOXML.
21903
+ */
21904
+ function findUnquotedExclamation(value) {
21905
+ let inQuote = false;
21906
+ for (let i = 0; i < value.length; i++) {
21907
+ const ch = value[i];
21908
+ if (ch === "'") {
21909
+ if (inQuote && value[i + 1] === "'") {
21910
+ i++;
21911
+ continue;
21912
+ }
21913
+ inQuote = !inQuote;
21914
+ } else if (ch === "!" && !inQuote) return i;
21915
+ }
21916
+ return -1;
21917
+ }
21918
+ /**
21919
+ * Parse a row token (the digits portion of a cell reference, or a
21920
+ * whole-row number) into a canonical integer string.
21921
+ *
21922
+ * Rejects:
21923
+ * - row 0 (Excel rows are 1-indexed; `$A$0` is never a valid Excel ref)
21924
+ * - rows beyond `EXCEL_MAX_ROW` (Excel hard limit)
21925
+ *
21926
+ * Normalises:
21927
+ * - leading zeros (`001` → `1`) — OOXML expects bare integers, and
21928
+ * `Number(...)` collapses any padding the user typed.
21929
+ */
21930
+ function parseRowToken(token) {
21931
+ const n = Number(token);
21932
+ if (n < 1) throw new RowOutOfBoundsError(n, `Excel rows are 1-indexed; row ${token} is invalid`);
21933
+ if (n > EXCEL_MAX_ROW$1) throw new RowOutOfBoundsError(n, `Excel supports rows from 1 to ${EXCEL_MAX_ROW$1}`);
21934
+ return String(n);
21935
+ }
21936
+ /**
21937
+ * Parse a single user- or OOXML-supplied print reference into one of
21938
+ * Excel's four valid shapes (cell / range / row / column), discarding
21939
+ * any sheet prefix the input carries. Both the writer and the reader
21940
+ * route through this single parser so the two sides agree on what is
21941
+ * accepted and how it is canonicalised.
21942
+ *
21943
+ * Accepts every form Excel itself accepts on input, regardless of which
21944
+ * side calls it:
21945
+ * - cell: `A1`, `$A$1`, `Sheet1!$A$1`, `'Q,F'!$A$1`, `a1`
21946
+ * - range: `A1:B5`, `$A$1:$B$5`, `Sheet!A1:B5`, ` A1 : B5 `, `a1:b5`,
21947
+ * mixed `$A1:$B$5`, reversed `B5:A1` (canonicalised to `A1:B5`)
21948
+ * - whole row: `1:5`, `$1:$5`, `Sheet!$1:$5`, `5` (single row),
21949
+ * reversed `5:1` (canonicalised to `1:5`), padded `001:005`
21950
+ * - whole column: `A:C`, `$A:$C`, `Sheet!$A:$C`, `C` (single column),
21951
+ * `a:c`, reversed `C:A` (canonicalised to `A:C`)
21952
+ *
21953
+ * Returns `undefined` for inputs that do not match one of the four
21954
+ * shapes — callers drop the entry rather than emit corrupt XML.
21955
+ *
21956
+ * **Throws**:
21957
+ * - `ColumnOutOfBoundsError` when the input parses as a valid shape
21958
+ * but references a column letter beyond Excel's XFD (16384) limit.
21959
+ * - `RowOutOfBoundsError` for row 0 (Excel rows are 1-indexed) or
21960
+ * rows beyond Excel's `1048576` limit.
21961
+ *
21962
+ * Both errors match what `getCell` and `colCache.l2n` already throw for
21963
+ * the same inputs; surfacing them here means a user who hand-authors a
21964
+ * malformed `printArea` finds out at write time rather than producing
21965
+ * a workbook Excel silently rejects.
21966
+ *
21967
+ * Why a hand-rolled parser instead of `colCache.decodeEx`? `decodeEx`
21968
+ * was designed for cell addresses and produces `NaN`-laced output for
21969
+ * whole-row (`$1:$5`) and whole-column (`$A:$C`) references. Print
21970
+ * areas and print titles legitimately use both, so we need a parser
21971
+ * that recognises all four shapes uniformly *and* canonicalises
21972
+ * reversed endpoints (which `decodeEx` does for cells but not for
21973
+ * row/column references).
21974
+ */
21975
+ function parsePrintReference(input) {
21976
+ if (typeof input !== "string") return;
21977
+ const trimmed = input.trim();
21978
+ if (!trimmed) return;
21979
+ const exclamation = findUnquotedExclamation(trimmed);
21980
+ const cleaned = (exclamation === -1 ? trimmed : trimmed.slice(exclamation + 1)).replace(/[\s$]+/g, "").toUpperCase();
21981
+ if (!cleaned) return;
21982
+ const parts = cleaned.split(":");
21983
+ if (parts.length > 2) return;
21984
+ const startRaw = parts[0];
21985
+ const endRaw = parts.length === 2 ? parts[1] : startRaw;
21986
+ const cellRe = /^([A-Z]+)(\d+)$/;
21987
+ const startCell = cellRe.exec(startRaw);
21988
+ const endCell = cellRe.exec(endRaw);
21989
+ if (startCell && endCell) {
21990
+ const startColNum = colCache.l2n(startCell[1]);
21991
+ const endColNum = colCache.l2n(endCell[1]);
21992
+ const startRow = Number(parseRowToken(startCell[2]));
21993
+ const endRow = Number(parseRowToken(endCell[2]));
21994
+ const tlCol = startColNum <= endColNum ? startCell[1] : endCell[1];
21995
+ const brCol = startColNum <= endColNum ? endCell[1] : startCell[1];
21996
+ const tlRow = startRow <= endRow ? startRow : endRow;
21997
+ const brRow = startRow <= endRow ? endRow : startRow;
21998
+ if (parts.length === 1) return {
21999
+ kind: "cell",
22000
+ ooxml: `$${tlCol}$${tlRow}`,
22001
+ dimensions: `${tlCol}${tlRow}`
22002
+ };
22003
+ return {
22004
+ kind: "range",
22005
+ ooxml: `$${tlCol}$${tlRow}:$${brCol}$${brRow}`,
22006
+ dimensions: `${tlCol}${tlRow}:${brCol}${brRow}`
22007
+ };
22008
+ }
22009
+ if (/^\d+$/.test(startRaw) && /^\d+$/.test(endRaw)) {
22010
+ const startRow = Number(parseRowToken(startRaw));
22011
+ const endRow = Number(parseRowToken(endRaw));
22012
+ const tl = Math.min(startRow, endRow);
22013
+ const br = Math.max(startRow, endRow);
22014
+ return {
22015
+ kind: "row",
22016
+ ooxml: `$${tl}:$${br}`,
22017
+ dimensions: `${tl}:${br}`
22018
+ };
22019
+ }
22020
+ if (/^[A-Z]+$/.test(startRaw) && /^[A-Z]+$/.test(endRaw)) {
22021
+ const startNum = colCache.l2n(startRaw);
22022
+ const endNum = colCache.l2n(endRaw);
22023
+ const tl = startNum <= endNum ? startRaw : endRaw;
22024
+ const br = startNum <= endNum ? endRaw : startRaw;
22025
+ return {
22026
+ kind: "col",
22027
+ ooxml: `$${tl}:$${br}`,
22028
+ dimensions: `${tl}:${br}`
22029
+ };
22030
+ }
22031
+ }
22032
+ /**
22033
+ * Normalise a user-supplied `printArea` value into the canonical OOXML
22034
+ * `'Sheet'!<ref>` form. Returns `undefined` for malformed input so the
22035
+ * caller drops the entry instead of emitting corrupt XML.
22036
+ *
22037
+ * `printArea` accepts cell, range, whole-row, and whole-column shapes —
22038
+ * Excel itself supports all four (e.g. selecting entire columns A:C as
22039
+ * the print area is a common UI gesture). Bare cell inputs are promoted
22040
+ * to a degenerate range `$A$1:$A$1` because that is what Excel itself
22041
+ * emits for a single-cell print area, and the worksheet API exposes
22042
+ * `printArea` as a range string (single-cell entries surface as `A1:A1`).
22043
+ */
22044
+ function normalisePrintAreaRange(input, sheetName) {
22045
+ const ref = parsePrintReference(input);
22046
+ if (!ref) return;
22047
+ const ooxml = ref.kind === "cell" ? `${ref.ooxml}:${ref.ooxml}` : ref.ooxml;
22048
+ return `${quoteSheetName(sheetName)}!${ooxml}`;
22049
+ }
22050
+ /**
22051
+ * Normalise a user-supplied print-titles row or column expression into
22052
+ * the canonical OOXML form `'Sheet'!$N:$N` (rows) or `'Sheet'!$L:$L`
22053
+ * (columns).
22054
+ *
22055
+ * Long-standing excelts behaviour lets users put a column expression on
22056
+ * `printTitlesRow` (and vice versa) — the OOXML reader has always
22057
+ * re-classified the value onto the correct field on round-trip — so we
22058
+ * honour that by letting the parser infer the actual axis from the
22059
+ * input shape. Strict enforcement would silently drop print titles
22060
+ * users have set successfully for years.
22061
+ */
22062
+ function normalisePrintTitlesAxis(input, sheetName) {
22063
+ const ref = parsePrintReference(input);
22064
+ if (!ref || ref.kind !== "row" && ref.kind !== "col") return;
22065
+ return `${quoteSheetName(sheetName)}!${ref.ooxml}`;
22066
+ }
22067
+ var WorkbookXform, EXCEL_MAX_ROW$1;
21830
22068
  var init_workbook_xform = __esmMin((() => {
22069
+ init_errors$4();
21831
22070
  init_col_cache();
21832
22071
  init_ooxml_paths();
21833
22072
  init_base_xform();
@@ -21898,37 +22137,33 @@ onmessage = async (ev) => {
21898
22137
  const printAreas = [];
21899
22138
  let index = 0;
21900
22139
  model.sheets.forEach((sheet) => {
21901
- if (sheet.pageSetup && sheet.pageSetup.printArea) sheet.pageSetup.printArea.split("&&").forEach((printArea) => {
21902
- const printAreaComponents = printArea.split(":");
21903
- const start = printAreaComponents[0];
21904
- const end = printAreaComponents[1] ?? start;
21905
- const definedName = {
22140
+ if (sheet.pageSetup && sheet.pageSetup.printArea) {
22141
+ const ranges = [];
22142
+ for (const segment of splitPrintAreaInput(sheet.pageSetup.printArea)) {
22143
+ const normalised = normalisePrintAreaRange(segment, sheet.name);
22144
+ if (normalised) ranges.push(normalised);
22145
+ }
22146
+ if (ranges.length > 0) printAreas.push({
21906
22147
  name: "_xlnm.Print_Area",
21907
- ranges: [`'${sheet.name}'!$${start}:$${end}`],
22148
+ ranges,
21908
22149
  localSheetId: index
21909
- };
21910
- printAreas.push(definedName);
21911
- });
22150
+ });
22151
+ }
21912
22152
  if (sheet.pageSetup && (sheet.pageSetup.printTitlesRow || sheet.pageSetup.printTitlesColumn)) {
21913
22153
  const ranges = [];
21914
22154
  if (sheet.pageSetup.printTitlesColumn) {
21915
- const titlesColumns = sheet.pageSetup.printTitlesColumn.split(":");
21916
- const start = titlesColumns[0];
21917
- const end = titlesColumns[1] ?? start;
21918
- ranges.push(`'${sheet.name}'!$${start}:$${end}`);
22155
+ const normalised = normalisePrintTitlesAxis(sheet.pageSetup.printTitlesColumn, sheet.name);
22156
+ if (normalised) ranges.push(normalised);
21919
22157
  }
21920
22158
  if (sheet.pageSetup.printTitlesRow) {
21921
- const titlesRows = sheet.pageSetup.printTitlesRow.split(":");
21922
- const start = titlesRows[0];
21923
- const end = titlesRows[1] ?? start;
21924
- ranges.push(`'${sheet.name}'!$${start}:$${end}`);
22159
+ const normalised = normalisePrintTitlesAxis(sheet.pageSetup.printTitlesRow, sheet.name);
22160
+ if (normalised) ranges.push(normalised);
21925
22161
  }
21926
- const definedName = {
22162
+ if (ranges.length > 0) printAreas.push({
21927
22163
  name: "_xlnm.Print_Titles",
21928
22164
  ranges,
21929
22165
  localSheetId: index
21930
- };
21931
- printAreas.push(definedName);
22166
+ });
21932
22167
  }
21933
22168
  index++;
21934
22169
  });
@@ -22053,13 +22288,26 @@ onmessage = async (ev) => {
22053
22288
  if (Array.isArray(model.worksheets)) model.worksheets = model.worksheets.filter((ws) => ws && Number.isInteger(ws.id) && ws.id > 0);
22054
22289
  const definedNames = [];
22055
22290
  if (model.definedNames) model.definedNames.forEach((definedName) => {
22056
- const effectiveRanges = definedName.ranges?.length > 0 ? definedName.ranges : definedName.rawText ? [definedName.rawText] : [];
22291
+ const effectiveRanges = definedName.ranges?.length > 0 ? definedName.ranges : definedName.rawText ? splitPrintAreaInput(definedName.rawText) : [];
22057
22292
  if (definedName.name === "_xlnm.Print_Area") {
22058
22293
  worksheet = worksheets[definedName.localSheetId];
22059
22294
  if (worksheet && effectiveRanges.length > 0) {
22060
22295
  if (!worksheet.pageSetup) worksheet.pageSetup = {};
22061
- const range = colCache.decodeEx(effectiveRanges[0]);
22062
- worksheet.pageSetup.printArea = worksheet.pageSetup.printArea ? `${worksheet.pageSetup.printArea}&&${range.dimensions}` : range.dimensions;
22296
+ const decoded = [];
22297
+ for (const rangeStr of effectiveRanges) {
22298
+ let ref;
22299
+ try {
22300
+ ref = parsePrintReference(rangeStr);
22301
+ } catch {
22302
+ ref = void 0;
22303
+ }
22304
+ if (!ref) continue;
22305
+ decoded.push(ref.kind === "cell" ? `${ref.dimensions}:${ref.dimensions}` : ref.dimensions);
22306
+ }
22307
+ if (decoded.length > 0) {
22308
+ const joined = decoded.join("&&");
22309
+ worksheet.pageSetup.printArea = worksheet.pageSetup.printArea ? `${worksheet.pageSetup.printArea}&&${joined}` : joined;
22310
+ }
22063
22311
  }
22064
22312
  } else if (definedName.name === "_xlnm.Print_Titles") {
22065
22313
  worksheet = worksheets[definedName.localSheetId];
@@ -22106,6 +22354,7 @@ onmessage = async (ev) => {
22106
22354
  }) };
22107
22355
  }
22108
22356
  };
22357
+ EXCEL_MAX_ROW$1 = 1048576;
22109
22358
  }));
22110
22359
  //#endregion
22111
22360
  //#region src/modules/excel/xlsx/xform/core/metadata-xform.ts