@a3s-lab/office 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13727,6 +13727,13 @@
13727
13727
  1
13728
13728
  ]
13729
13729
  ],
13730
+ [
13731
+ 'SUBTOTAL',
13732
+ [
13733
+ 2,
13734
+ 255
13735
+ ]
13736
+ ],
13730
13737
  [
13731
13738
  'SUM',
13732
13739
  [
@@ -13747,6 +13754,90 @@
13747
13754
  while(normalized.startsWith('_XLFN.') || normalized.startsWith('_XLWS.'))normalized = normalized.slice(6);
13748
13755
  return normalized;
13749
13756
  }
13757
+ function evaluateParserSubtotal(parameters) {
13758
+ const codeValue = parserNumericValue(parameters[0]);
13759
+ if (void 0 === codeValue) return 'VALUE!';
13760
+ const code = Math.trunc(codeValue);
13761
+ const values = parameters.slice(1).flatMap((parameter)=>flattenParserValues(parameter));
13762
+ for (const value of values){
13763
+ const error = parserErrorValue(value);
13764
+ if (error) return error;
13765
+ }
13766
+ switch(code){
13767
+ case 1:
13768
+ case 101:
13769
+ return parserSubtotalNumeric(values, 'average');
13770
+ case 2:
13771
+ case 102:
13772
+ return values.filter((value)=>'number' == typeof value).length;
13773
+ case 3:
13774
+ case 103:
13775
+ return values.filter((value)=>null != value).length;
13776
+ case 4:
13777
+ case 104:
13778
+ return parserSubtotalNumeric(values, 'max');
13779
+ case 5:
13780
+ case 105:
13781
+ return parserSubtotalNumeric(values, 'min');
13782
+ case 6:
13783
+ case 106:
13784
+ return parserSubtotalNumeric(values, 'product');
13785
+ case 7:
13786
+ case 107:
13787
+ return parserSubtotalNumeric(values, 'stddev');
13788
+ case 8:
13789
+ case 108:
13790
+ return parserSubtotalNumeric(values, 'stddevp');
13791
+ case 9:
13792
+ case 109:
13793
+ return parserSubtotalNumeric(values, 'sum');
13794
+ case 10:
13795
+ case 110:
13796
+ return parserSubtotalNumeric(values, 'var');
13797
+ case 11:
13798
+ case 111:
13799
+ return parserSubtotalNumeric(values, 'varp');
13800
+ default:
13801
+ return 'VALUE!';
13802
+ }
13803
+ }
13804
+ function parserSubtotalNumeric(values, operation) {
13805
+ const numbers = values.filter((value)=>'number' == typeof value && Number.isFinite(value));
13806
+ const count = numbers.length;
13807
+ const sum = numbers.reduce((total, value)=>total + value, 0);
13808
+ if ('sum' === operation) return finiteParserNumber(sum);
13809
+ if ('average' === operation) return count ? finiteParserNumber(sum / count) : 'DIV/0!';
13810
+ if ('max' === operation) return finiteParserNumber(Math.max(...numbers, 0));
13811
+ if ('min' === operation) return finiteParserNumber(Math.min(...numbers, 0));
13812
+ if ('product' === operation) return finiteParserNumber(count ? numbers.reduce((total, value)=>total * value, 1) : 0);
13813
+ if ('stddev' === operation || 'var' === operation) {
13814
+ if (count < 2) return 'DIV/0!';
13815
+ } else if (0 === count) return 'DIV/0!';
13816
+ const mean = sum / count;
13817
+ const divisor = 'stddev' === operation || 'var' === operation ? count - 1 : count;
13818
+ const variance = numbers.reduce((total, value)=>total + (value - mean) ** 2, 0) / divisor;
13819
+ return finiteParserNumber('stddev' === operation || 'stddevp' === operation ? Math.sqrt(variance) : variance);
13820
+ }
13821
+ function flattenParserValues(value) {
13822
+ if (!Array.isArray(value)) return [
13823
+ value
13824
+ ];
13825
+ return value.flatMap((entry)=>flattenParserValues(entry));
13826
+ }
13827
+ function parserNumericValue(value) {
13828
+ if ('number' == typeof value) return Number.isFinite(value) ? value : void 0;
13829
+ if ('boolean' == typeof value) return value ? 1 : 0;
13830
+ if ('string' != typeof value || !value.trim()) return;
13831
+ const parsed = Number(value);
13832
+ return Number.isFinite(parsed) ? parsed : void 0;
13833
+ }
13834
+ function parserErrorValue(value) {
13835
+ if (value instanceof Error) return value.message.startsWith('#') ? value.message.slice(1) : 'VALUE!';
13836
+ return 'string' == typeof value && value.startsWith('#') ? value.slice(1) : void 0;
13837
+ }
13838
+ function finiteParserNumber(value) {
13839
+ return Number.isFinite(value) ? value : 'NUM!';
13840
+ }
13750
13841
  function normalizeFormulaForFortuneParser(formula) {
13751
13842
  const source = formula.replace(/^=/, '');
13752
13843
  let output = '';
@@ -13916,6 +14007,7 @@
13916
14007
  if (!sheet.id.trim() || utf8ByteLength(sheet.id) > MAX_SPREADSHEET_IDENTIFIER_BYTES || !sheet.name.trim() || utf8ByteLength(sheet.name) > MAX_SPREADSHEET_IDENTIFIER_BYTES || sheetIds.has(sheet.id) || sheetNames.has(normalizedName)) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.sheet_invalid', 'Spreadsheet sheet IDs and names must be unique and non-empty.');
13917
14008
  sheetIds.add(sheet.id);
13918
14009
  sheetNames.add(normalizedName);
14010
+ validateSpreadsheetTables(sheet, request.sheets);
13919
14011
  const coordinates = new Set();
13920
14012
  for (const cell of sheet.cells){
13921
14013
  const key = `${cell.row}:${cell.column}`;
@@ -13927,6 +14019,68 @@
13927
14019
  }
13928
14020
  for (const target of request.targets ?? [])if (!sheetIds.has(target.sheetId) || !boundedSpreadsheetIndex(target.row, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS) || !boundedSpreadsheetIndex(target.column, 16384)) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.target_invalid', 'Spreadsheet calculation targets must reference an existing, bounded cell.');
13929
14021
  }
14022
+ function validateSpreadsheetTables(sheet, sheets) {
14023
+ const tables = sheet.tables ?? [];
14024
+ const tableCount = sheets.reduce((count, candidate)=>count + (candidate.tables?.length ?? 0), 0);
14025
+ if (tableCount > 1024) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_limit_exceeded', 'A Spreadsheet calculation request may contain at most 1024 tables.');
14026
+ const aliases = new Map();
14027
+ for (const candidate of sheets)for (const [tableIndex, table] of (candidate.tables ?? []).entries()){
14028
+ const identity = `${candidate.id}\u0000${tableIndex}`;
14029
+ for (const alias of [
14030
+ table.name,
14031
+ table.displayName
14032
+ ]){
14033
+ if (!alias) continue;
14034
+ const normalized = alias.toLocaleLowerCase();
14035
+ const existing = aliases.get(normalized);
14036
+ if (existing && existing !== identity) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet table name '${alias}' is ambiguous.`);
14037
+ aliases.set(normalized, identity);
14038
+ }
14039
+ }
14040
+ const ranges = [];
14041
+ for (const table of tables){
14042
+ for (const [kind, value] of [
14043
+ [
14044
+ 'startRow',
14045
+ table.startRow
14046
+ ],
14047
+ [
14048
+ 'endRow',
14049
+ table.endRow
14050
+ ],
14051
+ [
14052
+ 'startColumn',
14053
+ table.startColumn
14054
+ ],
14055
+ [
14056
+ 'endColumn',
14057
+ table.endColumn
14058
+ ]
14059
+ ])if (!boundedSpreadsheetIndex(value, kind.endsWith('Row') ? OFFICE_KERNEL_SPREADSHEET_MAX_ROWS : 16384)) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet table '${table.name}' has an out-of-bounds ${kind}.`);
14060
+ if (table.startRow > table.endRow || table.startColumn > table.endColumn || table.columns.length !== table.endColumn - table.startColumn + 1) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet table '${table.name}' has an invalid range or column count.`);
14061
+ validateTableName(table.name, 'name');
14062
+ if (void 0 !== table.displayName) validateTableName(table.displayName, 'displayName');
14063
+ const columnNames = new Set();
14064
+ for (const column of table.columns){
14065
+ validateTableName(column, 'column');
14066
+ const normalized = column.toLocaleLowerCase();
14067
+ if (columnNames.has(normalized)) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet table '${table.name}' contains duplicate column names.`);
14068
+ columnNames.add(normalized);
14069
+ }
14070
+ if (table.headerRow && table.totalsRow && table.startRow === table.endRow) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet table '${table.name}' cannot use header and totals rows in one row.`);
14071
+ for (const previous of ranges)if (table.startRow <= previous.endRow && table.endRow >= previous.startRow && table.startColumn <= previous.endColumn && table.endColumn >= previous.startColumn) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet tables '${previous.name}' and '${table.name}' overlap.`);
14072
+ ranges.push({
14073
+ startRow: table.startRow,
14074
+ endRow: table.endRow,
14075
+ startColumn: table.startColumn,
14076
+ endColumn: table.endColumn,
14077
+ name: table.name
14078
+ });
14079
+ }
14080
+ }
14081
+ function validateTableName(value, kind) {
14082
+ if (!value.trim() || utf8ByteLength(value) > MAX_SPREADSHEET_IDENTIFIER_BYTES) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet table ${kind} must contain 1-${MAX_SPREADSHEET_IDENTIFIER_BYTES} UTF-8 bytes.`);
14083
+ }
13930
14084
  function boundedSpreadsheetIndex(value, exclusiveMaximum) {
13931
14085
  return Number.isSafeInteger(value) && value >= 0 && value < exclusiveMaximum;
13932
14086
  }
@@ -14020,6 +14174,428 @@
14020
14174
  const withSuffix = '#DIV/0' === normalized ? '#DIV/0!' : normalized;
14021
14175
  return isOfficeKernelSpreadsheetError(withSuffix);
14022
14176
  }
14177
+ class SpreadsheetStructuredReferenceError extends Error {
14178
+ kind;
14179
+ constructor(kind, message){
14180
+ super(message);
14181
+ this.name = 'SpreadsheetStructuredReferenceError';
14182
+ this.kind = kind;
14183
+ }
14184
+ }
14185
+ function parseSpreadsheetStructuredReference(reference) {
14186
+ const open = reference.indexOf('[');
14187
+ if (open < 0) throw invalidReference(reference);
14188
+ const tableName = reference.slice(0, open) || void 0;
14189
+ const content = outerGroup(reference.slice(open));
14190
+ if (null === content) throw invalidReference(reference);
14191
+ const rows = {
14192
+ all: false,
14193
+ headers: false,
14194
+ data: false,
14195
+ totals: false,
14196
+ current: false
14197
+ };
14198
+ let firstColumn;
14199
+ let lastColumn;
14200
+ if (content.startsWith('@')) {
14201
+ rows.current = true;
14202
+ const column = parseCurrentColumn(content.slice(1), reference);
14203
+ firstColumn = column;
14204
+ lastColumn = column;
14205
+ } else if (content.startsWith('[')) [firstColumn, lastColumn] = parseNestedSelection(content, reference, rows);
14206
+ else {
14207
+ const item = tableItem(content);
14208
+ if (item) applyTableItem(rows, item, reference);
14209
+ else {
14210
+ const column = parsePlainColumn(content, reference);
14211
+ firstColumn = column;
14212
+ lastColumn = column;
14213
+ }
14214
+ }
14215
+ if (!rows.all && !rows.headers && !rows.data && !rows.totals && !rows.current) rows.data = true;
14216
+ return {
14217
+ tableName,
14218
+ firstColumn,
14219
+ lastColumn,
14220
+ rows
14221
+ };
14222
+ }
14223
+ class SpreadsheetStructuredReferenceCatalog {
14224
+ sheets;
14225
+ definitions = [];
14226
+ byName = new Map();
14227
+ bySheet = new Map();
14228
+ constructor(sheets){
14229
+ this.sheets = sheets;
14230
+ for (const sheet of sheets){
14231
+ const indexes = [];
14232
+ for (const table of sheet.tables ?? []){
14233
+ const definition = {
14234
+ ...table,
14235
+ sheetId: sheet.id,
14236
+ sheetName: sheet.name
14237
+ };
14238
+ const index = this.definitions.length;
14239
+ this.definitions.push(definition);
14240
+ indexes.push(index);
14241
+ for (const alias of [
14242
+ table.name,
14243
+ table.displayName
14244
+ ]){
14245
+ if (!alias) continue;
14246
+ const key = alias.toLocaleLowerCase();
14247
+ if (!this.byName.has(key)) this.byName.set(key, index);
14248
+ }
14249
+ }
14250
+ this.bySheet.set(sheet.id, indexes);
14251
+ }
14252
+ }
14253
+ resolve(qualifier, reference, currentSheet, currentColumn, currentRow) {
14254
+ const parsed = parseSpreadsheetStructuredReference(reference);
14255
+ const table = this.resolveTable(parsed, reference, currentSheet, currentColumn, currentRow);
14256
+ if (qualifier && table.sheetName.toLocaleLowerCase() !== qualifier.toLocaleLowerCase()) throw new SpreadsheetStructuredReferenceError('missing-table', `Spreadsheet table '${table.name}' is not on worksheet '${qualifier}'.`);
14257
+ if (parsed.rows.current && currentSheet.id !== table.sheetId) throw new SpreadsheetStructuredReferenceError('unsupported', `Structured reference #This Row requires the current formula cell to be on table '${table.name}'.`);
14258
+ const [startColumn, endColumn] = this.resolveColumns(table, parsed);
14259
+ return this.resolveRows(table, parsed.rows, currentRow).map(([startRow, endRow])=>({
14260
+ sheetId: table.sheetId,
14261
+ sheetName: table.sheetName,
14262
+ startRow,
14263
+ endRow,
14264
+ startColumn,
14265
+ endColumn
14266
+ }));
14267
+ }
14268
+ resolveTable(parsed, reference, currentSheet, currentColumn, currentRow) {
14269
+ if (parsed.tableName) {
14270
+ const index = this.byName.get(parsed.tableName.toLocaleLowerCase());
14271
+ if (void 0 === index) throw new SpreadsheetStructuredReferenceError('missing-table', `Spreadsheet table '${parsed.tableName}' does not exist.`);
14272
+ const table = this.definitions[index];
14273
+ if (!table) throw invalidReference(reference);
14274
+ return table;
14275
+ }
14276
+ const matching = (this.bySheet.get(currentSheet.id) ?? []).map((index)=>this.definitions[index]).filter((table)=>void 0 !== table && currentRow >= table.startRow && currentRow <= table.endRow && currentColumn >= table.startColumn && currentColumn <= table.endColumn);
14277
+ if (!matching.length) throw new SpreadsheetStructuredReferenceError('missing-table', `Table-local structured reference '${reference}' requires the current formula cell to be inside a Spreadsheet table.`);
14278
+ if (matching.length > 1) throw new SpreadsheetStructuredReferenceError('unsupported', `Table-local structured reference '${reference}' is ambiguous at the current formula cell.`);
14279
+ return matching[0];
14280
+ }
14281
+ resolveColumns(table, parsed) {
14282
+ let first = 0;
14283
+ let last = table.columns.length - 1;
14284
+ if (void 0 !== parsed.firstColumn && void 0 !== parsed.lastColumn) {
14285
+ first = table.columns.findIndex((column)=>column.toLocaleLowerCase() === parsed.firstColumn.toLocaleLowerCase());
14286
+ last = table.columns.findIndex((column)=>column.toLocaleLowerCase() === parsed.lastColumn.toLocaleLowerCase());
14287
+ if (first < 0) throw new SpreadsheetStructuredReferenceError('missing-column', `Spreadsheet table '${table.name}' has no column '${parsed.firstColumn}'.`);
14288
+ if (last < 0) throw new SpreadsheetStructuredReferenceError('missing-column', `Spreadsheet table '${table.name}' has no column '${parsed.lastColumn}'.`);
14289
+ if (first > last) throw new SpreadsheetStructuredReferenceError('unsupported', `Structured-reference column range '${parsed.firstColumn}:${parsed.lastColumn}' is reversed.`);
14290
+ }
14291
+ if (first < 0 || last < first || table.startColumn + last > table.endColumn) throw invalidReference(table.name);
14292
+ return [
14293
+ table.startColumn + first,
14294
+ table.startColumn + last
14295
+ ];
14296
+ }
14297
+ resolveRows(table, rows, currentRow) {
14298
+ const selected = [];
14299
+ if (rows.all) selected.push([
14300
+ table.startRow,
14301
+ table.endRow
14302
+ ]);
14303
+ if (rows.headers) {
14304
+ if (!table.headerRow) throw missingRows(table, '#Headers');
14305
+ selected.push([
14306
+ table.startRow,
14307
+ table.startRow
14308
+ ]);
14309
+ }
14310
+ if (rows.data) selected.push(this.dataRows(table));
14311
+ if (rows.totals) {
14312
+ if (!table.totalsRow) throw missingRows(table, '#Totals');
14313
+ selected.push([
14314
+ table.endRow,
14315
+ table.endRow
14316
+ ]);
14317
+ }
14318
+ if (rows.current) {
14319
+ const [start, end] = this.dataRows(table);
14320
+ if (currentRow < start || currentRow > end) throw new SpreadsheetStructuredReferenceError('unsupported', `Structured reference #This Row requires the current formula row to be inside table '${table.name}'.`);
14321
+ selected.push([
14322
+ currentRow,
14323
+ currentRow
14324
+ ]);
14325
+ }
14326
+ selected.sort((left, right)=>left[0] - right[0]);
14327
+ const merged = [];
14328
+ for (const [start, end] of selected){
14329
+ const previous = merged.at(-1);
14330
+ if (previous && start <= previous[1] + 1) previous[1] = Math.max(previous[1], end);
14331
+ else merged.push([
14332
+ start,
14333
+ end
14334
+ ]);
14335
+ }
14336
+ if (!merged.length) throw new SpreadsheetStructuredReferenceError('unsupported', 'Structured reference selects no table rows.');
14337
+ return merged;
14338
+ }
14339
+ dataRows(table) {
14340
+ const start = table.startRow + (table.headerRow ? 1 : 0);
14341
+ const end = table.endRow - (table.totalsRow ? 1 : 0);
14342
+ if (start > end) throw new SpreadsheetStructuredReferenceError('unsupported', `Spreadsheet table '${table.name}' has no data rows.`);
14343
+ return [
14344
+ start,
14345
+ end
14346
+ ];
14347
+ }
14348
+ }
14349
+ function expandSpreadsheetStructuredReferences(formula, catalog, currentSheet, currentRow, currentColumn) {
14350
+ const hasEquals = formula.startsWith('=');
14351
+ const source = hasEquals ? formula.slice(1) : formula;
14352
+ let output = '';
14353
+ let cursor = 0;
14354
+ while(cursor < source.length){
14355
+ const character = source[cursor] ?? '';
14356
+ if ('"' === character) {
14357
+ const end = quotedStringEnd(source, cursor);
14358
+ output += source.slice(cursor, end);
14359
+ cursor = end;
14360
+ continue;
14361
+ }
14362
+ const token = scanStructuredReference(source, cursor);
14363
+ if (!token) {
14364
+ output += character;
14365
+ cursor += 1;
14366
+ continue;
14367
+ }
14368
+ const areas = catalog.resolve(token.qualifier, token.reference, currentSheet, currentColumn, currentRow);
14369
+ if (areas.length > 1) throw new SpreadsheetStructuredReferenceError('unsupported', `Structured reference '${token.reference}' resolves to disjoint row areas; the JavaScript fallback requires a contiguous range.`);
14370
+ const ranges = areas.map((area)=>{
14371
+ const prefix = area.sheetId === currentSheet.id ? '' : `${quoteSheetName(area.sheetName)}!`;
14372
+ const start = cellAddress(area.startRow, area.startColumn);
14373
+ const end = cellAddress(area.endRow, area.endColumn);
14374
+ return `${prefix}${start}${start === end ? '' : `:${end}`}`;
14375
+ });
14376
+ output += 1 === ranges.length ? ranges[0] : `(${ranges.join(',')})`;
14377
+ cursor = token.end;
14378
+ }
14379
+ return hasEquals ? `=${output}` : output;
14380
+ }
14381
+ function scanStructuredReference(source, start) {
14382
+ const qualified = scanQualifier(source, start);
14383
+ let cursor = qualified?.end ?? start;
14384
+ const qualifier = qualified?.name;
14385
+ if ('[' === source[cursor]) {
14386
+ const end = matchingBracket(source, cursor);
14387
+ if (null === end) return null;
14388
+ const content = source.slice(cursor + 1, end - 1);
14389
+ if (!content.startsWith('@') && !content.startsWith('#') && !content.startsWith('[')) return null;
14390
+ return {
14391
+ end,
14392
+ qualifier,
14393
+ reference: source.slice(cursor, end)
14394
+ };
14395
+ }
14396
+ const nameStart = cursor;
14397
+ if (!isNameStart(source[cursor] ?? '')) return null;
14398
+ cursor += 1;
14399
+ while(cursor < source.length && isNameContinue(source[cursor]))cursor += 1;
14400
+ if ('[' !== source[cursor]) return null;
14401
+ const end = matchingBracket(source, cursor);
14402
+ if (null === end) throw invalidReference(source.slice(nameStart, source.length));
14403
+ return {
14404
+ end,
14405
+ qualifier,
14406
+ reference: source.slice(nameStart, end)
14407
+ };
14408
+ }
14409
+ function scanQualifier(source, start) {
14410
+ if ("'" === source[start]) {
14411
+ let cursor = start + 1;
14412
+ let decoded = '';
14413
+ while(cursor < source.length){
14414
+ const character = source[cursor];
14415
+ if ("'" === character) {
14416
+ if ("'" === source[cursor + 1]) {
14417
+ decoded += "'";
14418
+ cursor += 2;
14419
+ continue;
14420
+ }
14421
+ if ('!' === source[cursor + 1]) return {
14422
+ name: decoded,
14423
+ end: cursor + 2
14424
+ };
14425
+ break;
14426
+ }
14427
+ decoded += character;
14428
+ cursor += 1;
14429
+ }
14430
+ return null;
14431
+ }
14432
+ let cursor = start;
14433
+ while(cursor < source.length && isQualifierCharacter(source[cursor]))cursor += 1;
14434
+ if ('!' !== source[cursor] || cursor === start) return null;
14435
+ return {
14436
+ name: source.slice(start, cursor),
14437
+ end: cursor + 1
14438
+ };
14439
+ }
14440
+ function matchingBracket(source, start) {
14441
+ let depth = 0;
14442
+ for(let cursor = start; cursor < source.length; cursor += 1){
14443
+ const character = source[cursor];
14444
+ if ("'" === character) {
14445
+ cursor += "'" === source[cursor + 1] ? 1 : 0;
14446
+ continue;
14447
+ }
14448
+ if ('[' === character) depth += 1;
14449
+ else if (']' === character) {
14450
+ depth -= 1;
14451
+ if (0 === depth) return cursor + 1;
14452
+ if (depth < 0) break;
14453
+ }
14454
+ }
14455
+ return null;
14456
+ }
14457
+ function outerGroup(value) {
14458
+ if (!value.startsWith('[')) return null;
14459
+ const end = matchingBracket(value, 0);
14460
+ return end === value.length ? value.slice(1, -1) : null;
14461
+ }
14462
+ function bracketAtom(value) {
14463
+ if (!value.startsWith('[')) return null;
14464
+ const end = matchingBracket(value, 0);
14465
+ return null === end ? null : {
14466
+ atom: value.slice(1, end - 1),
14467
+ consumed: end
14468
+ };
14469
+ }
14470
+ function parseCurrentColumn(value, reference) {
14471
+ if (value.startsWith('[')) {
14472
+ const atom = bracketAtom(value);
14473
+ if (!atom || atom.consumed !== value.length) throw invalidReference(reference);
14474
+ const column = decodeAtom(atom.atom);
14475
+ if (!column) throw invalidReference(reference);
14476
+ return column;
14477
+ }
14478
+ return parsePlainColumn(value, reference);
14479
+ }
14480
+ function parseNestedSelection(content, reference, rows) {
14481
+ const atoms = [];
14482
+ const separators = [];
14483
+ let cursor = 0;
14484
+ while(cursor < content.length){
14485
+ const atom = bracketAtom(content.slice(cursor));
14486
+ if (!atom) throw invalidReference(reference);
14487
+ atoms.push(atom.atom);
14488
+ cursor += atom.consumed;
14489
+ if (cursor === content.length) break;
14490
+ const separator = content[cursor];
14491
+ if (',' !== separator && ':' !== separator) throw invalidReference(reference);
14492
+ separators.push(separator);
14493
+ cursor += 1;
14494
+ }
14495
+ const columns = [];
14496
+ atoms.forEach((atom, index)=>{
14497
+ const item = tableItem(atom);
14498
+ if (item) applyTableItem(rows, item, reference);
14499
+ else {
14500
+ const column = decodeAtom(atom);
14501
+ if (!column) throw invalidReference(reference);
14502
+ columns.push({
14503
+ index,
14504
+ name: column
14505
+ });
14506
+ }
14507
+ });
14508
+ if (0 === columns.length) {
14509
+ if (separators.includes(':')) throw invalidReference(reference);
14510
+ return [
14511
+ void 0,
14512
+ void 0
14513
+ ];
14514
+ }
14515
+ if (1 === columns.length) {
14516
+ if (separators.includes(':')) throw invalidReference(reference);
14517
+ return [
14518
+ columns[0].name,
14519
+ columns[0].name
14520
+ ];
14521
+ }
14522
+ const first = columns[0];
14523
+ const last = columns[1];
14524
+ if (2 === columns.length && last.index === first.index + 1 && ':' === separators[first.index] && separators.every((separator, index)=>index === first.index || ',' === separator)) return [
14525
+ first.name,
14526
+ last.name
14527
+ ];
14528
+ throw new SpreadsheetStructuredReferenceError('unsupported', 'Disjoint structured-reference columns are not supported.');
14529
+ }
14530
+ function tableItem(value) {
14531
+ const normalized = value.toLocaleLowerCase();
14532
+ if ('#all' === normalized) return 'all';
14533
+ if ('#headers' === normalized) return 'headers';
14534
+ if ('#data' === normalized) return 'data';
14535
+ if ('#totals' === normalized) return 'totals';
14536
+ if ('#this row' === normalized) return 'current';
14537
+ }
14538
+ function applyTableItem(rows, item, reference) {
14539
+ rows['current' === item ? 'current' : item] = true;
14540
+ if (rows.current && (rows.all || rows.headers || rows.data || rows.totals)) throw new SpreadsheetStructuredReferenceError('unsupported', `Structured reference '${reference}' cannot combine #This Row with another item.`);
14541
+ }
14542
+ function parsePlainColumn(value, reference) {
14543
+ if (!value || /[[\],:]/u.test(value)) throw invalidReference(reference);
14544
+ const column = decodeAtom(value);
14545
+ if (!column) throw invalidReference(reference);
14546
+ return column;
14547
+ }
14548
+ function decodeAtom(value) {
14549
+ let output = '';
14550
+ for(let cursor = 0; cursor < value.length; cursor += 1){
14551
+ const character = value[cursor];
14552
+ if ("'" === character) {
14553
+ const escaped = value[cursor + 1];
14554
+ if (void 0 === escaped) throw invalidReference(value);
14555
+ output += escaped;
14556
+ cursor += 1;
14557
+ } else output += character;
14558
+ }
14559
+ return output;
14560
+ }
14561
+ function missingRows(table, item) {
14562
+ return new SpreadsheetStructuredReferenceError('unsupported', `Structured reference ${item} requires table '${table.name}' to contain that row.`);
14563
+ }
14564
+ function invalidReference(reference) {
14565
+ return new SpreadsheetStructuredReferenceError('invalid', `Structured reference '${reference}' is not in a supported canonical form.`);
14566
+ }
14567
+ function quotedStringEnd(source, start) {
14568
+ for(let cursor = start + 1; cursor < source.length; cursor += 1)if ('"' === source[cursor]) {
14569
+ if ('"' === source[cursor + 1]) {
14570
+ cursor += 1;
14571
+ continue;
14572
+ }
14573
+ return cursor + 1;
14574
+ }
14575
+ return source.length;
14576
+ }
14577
+ function cellAddress(row, column) {
14578
+ let value = column + 1;
14579
+ let label = '';
14580
+ while(value > 0){
14581
+ value -= 1;
14582
+ label = String.fromCharCode(65 + value % 26) + label;
14583
+ value = Math.floor(value / 26);
14584
+ }
14585
+ return `${label}${row + 1}`;
14586
+ }
14587
+ function quoteSheetName(name) {
14588
+ return /^[A-Za-z_][A-Za-z0-9_.]*$/u.test(name) ? name : `'${name.replaceAll("'", "''")}'`;
14589
+ }
14590
+ function isNameStart(value) {
14591
+ return /^[A-Za-z_\\?]$/u.test(value);
14592
+ }
14593
+ function isNameContinue(value) {
14594
+ return /^[A-Za-z0-9_.\\?]$/u.test(value);
14595
+ }
14596
+ function isQualifierCharacter(value) {
14597
+ return /^[A-Za-z0-9_.\\?[\]:$]$/u.test(value);
14598
+ }
14023
14599
  async function calculateSpreadsheetInJavaScript(request) {
14024
14600
  validateSpreadsheetCalculationRequest(request);
14025
14601
  const formulaParser = await Promise.resolve().then(__webpack_require__.bind(__webpack_require__, 909));
@@ -14035,9 +14611,11 @@
14035
14611
  stack = [];
14036
14612
  calculationOrder = [];
14037
14613
  issues = [];
14614
+ tableCatalog;
14038
14615
  constructor(request, formulaParser){
14039
14616
  this.request = request;
14040
14617
  this.formulaParser = formulaParser;
14618
+ this.tableCatalog = new SpreadsheetStructuredReferenceCatalog(request.sheets);
14041
14619
  for (const sheet of request.sheets){
14042
14620
  this.sheetsByName.set(sheet.name.toLowerCase(), sheet);
14043
14621
  for (const cell of sheet.cells)this.cells.set(cellKey(sheet.id, cell.row, cell.column), {
@@ -14108,16 +14686,24 @@
14108
14686
  }
14109
14687
  evaluateFormula(coordinate, indexed) {
14110
14688
  const formula = indexed.cell.formula ?? '';
14689
+ let expandedFormula;
14690
+ try {
14691
+ expandedFormula = expandSpreadsheetStructuredReferences(formula, this.tableCatalog, indexed.sheet, coordinate.row, coordinate.column);
14692
+ } catch (error) {
14693
+ const message = error instanceof SpreadsheetStructuredReferenceError ? error.message : 'Structured reference expansion failed.';
14694
+ return failedEvaluation(indexed.cell.value, calculationIssue(coordinate, 'office.kernel.spreadsheet.formula_unsupported', message));
14695
+ }
14111
14696
  const parser = new this.formulaParser.Parser();
14112
14697
  let unresolvedDependency = false;
14113
14698
  let unsupportedReference = false;
14114
14699
  let unsupportedFunction;
14115
14700
  let materializedRangeCells = 0;
14116
14701
  parser.setFunction('IFERROR', evaluateParserIfError).setFunction('ROW', (parameters)=>parameters.length ? null : coordinate.row + 1).setFunction('COLUMN', (parameters)=>parameters.length ? null : coordinate.column + 1);
14117
- parser.on('callFunction', (name, parameters)=>{
14702
+ parser.on('callFunction', (name, parameters, done)=>{
14118
14703
  const normalized = normalizeSpreadsheetFunctionName(name);
14119
14704
  const arity = browserScalarFunctionArities.get(normalized);
14120
14705
  if (!arity || parameters.length < arity[0] || parameters.length > arity[1]) unsupportedFunction ??= name.toUpperCase();
14706
+ if ('SUBTOTAL' === normalized) done(evaluateParserSubtotal(parameters));
14121
14707
  });
14122
14708
  parser.on('callCellValue', (cell, _options, done)=>{
14123
14709
  const dependency = this.resolveCoordinate(indexed.sheet, cell);
@@ -14141,7 +14727,7 @@
14141
14727
  return true;
14142
14728
  }));
14143
14729
  });
14144
- const parsed = parser.parse(normalizeFormulaForFortuneParser(formula), {
14730
+ const parsed = parser.parse(normalizeFormulaForFortuneParser(expandedFormula), {
14145
14731
  sheetId: indexed.sheet.id
14146
14732
  });
14147
14733
  if (unsupportedFunction) return failedEvaluation(indexed.cell.value, calculationIssue(coordinate, 'office.kernel.spreadsheet.formula_unsupported', `Formula function '${unsupportedFunction}' is not supported.`));
@@ -14165,7 +14751,8 @@
14165
14751
  }
14166
14752
  rangeValues(currentSheet, start, end, onUnresolvedDependency, onUnsupportedReference, reserveRangeCells) {
14167
14753
  const startCoordinate = this.resolveCoordinate(currentSheet, start);
14168
- const endCoordinate = this.resolveCoordinate(currentSheet, end);
14754
+ const rangeSheet = start.sheetName ? this.sheetsByName.get(start.sheetName.toLowerCase()) : currentSheet;
14755
+ const endCoordinate = rangeSheet ? this.resolveCoordinate(rangeSheet, end) : null;
14169
14756
  if (!startCoordinate || !endCoordinate || startCoordinate.sheetId !== endCoordinate.sheetId) {
14170
14757
  onUnsupportedReference();
14171
14758
  return [];
@@ -14344,6 +14931,14 @@
14344
14931
  return sheets.map((sheet)=>({
14345
14932
  id: sheet.id,
14346
14933
  name: sheet.name,
14934
+ ...sheet.tables?.length ? {
14935
+ tables: sheet.tables.map((table)=>({
14936
+ ...table,
14937
+ columns: [
14938
+ ...table.columns
14939
+ ]
14940
+ }))
14941
+ } : {},
14347
14942
  cells: sheet.cells.map((cell)=>({
14348
14943
  ...cell,
14349
14944
  value: {