@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.
package/dist/0~7048.js CHANGED
@@ -1352,6 +1352,13 @@ const browserScalarFunctionArities = new Map([
1352
1352
  1
1353
1353
  ]
1354
1354
  ],
1355
+ [
1356
+ 'SUBTOTAL',
1357
+ [
1358
+ 2,
1359
+ 255
1360
+ ]
1361
+ ],
1355
1362
  [
1356
1363
  'SUM',
1357
1364
  [
@@ -1372,6 +1379,90 @@ function normalizeSpreadsheetFunctionName(name) {
1372
1379
  while(normalized.startsWith('_XLFN.') || normalized.startsWith('_XLWS.'))normalized = normalized.slice(6);
1373
1380
  return normalized;
1374
1381
  }
1382
+ function evaluateParserSubtotal(parameters) {
1383
+ const codeValue = parserNumericValue(parameters[0]);
1384
+ if (void 0 === codeValue) return 'VALUE!';
1385
+ const code = Math.trunc(codeValue);
1386
+ const values = parameters.slice(1).flatMap((parameter)=>flattenParserValues(parameter));
1387
+ for (const value of values){
1388
+ const error = parserErrorValue(value);
1389
+ if (error) return error;
1390
+ }
1391
+ switch(code){
1392
+ case 1:
1393
+ case 101:
1394
+ return parserSubtotalNumeric(values, 'average');
1395
+ case 2:
1396
+ case 102:
1397
+ return values.filter((value)=>'number' == typeof value).length;
1398
+ case 3:
1399
+ case 103:
1400
+ return values.filter((value)=>null != value).length;
1401
+ case 4:
1402
+ case 104:
1403
+ return parserSubtotalNumeric(values, 'max');
1404
+ case 5:
1405
+ case 105:
1406
+ return parserSubtotalNumeric(values, 'min');
1407
+ case 6:
1408
+ case 106:
1409
+ return parserSubtotalNumeric(values, 'product');
1410
+ case 7:
1411
+ case 107:
1412
+ return parserSubtotalNumeric(values, 'stddev');
1413
+ case 8:
1414
+ case 108:
1415
+ return parserSubtotalNumeric(values, 'stddevp');
1416
+ case 9:
1417
+ case 109:
1418
+ return parserSubtotalNumeric(values, 'sum');
1419
+ case 10:
1420
+ case 110:
1421
+ return parserSubtotalNumeric(values, 'var');
1422
+ case 11:
1423
+ case 111:
1424
+ return parserSubtotalNumeric(values, 'varp');
1425
+ default:
1426
+ return 'VALUE!';
1427
+ }
1428
+ }
1429
+ function parserSubtotalNumeric(values, operation) {
1430
+ const numbers = values.filter((value)=>'number' == typeof value && Number.isFinite(value));
1431
+ const count = numbers.length;
1432
+ const sum = numbers.reduce((total, value)=>total + value, 0);
1433
+ if ('sum' === operation) return finiteParserNumber(sum);
1434
+ if ('average' === operation) return count ? finiteParserNumber(sum / count) : 'DIV/0!';
1435
+ if ('max' === operation) return finiteParserNumber(Math.max(...numbers, 0));
1436
+ if ('min' === operation) return finiteParserNumber(Math.min(...numbers, 0));
1437
+ if ('product' === operation) return finiteParserNumber(count ? numbers.reduce((total, value)=>total * value, 1) : 0);
1438
+ if ('stddev' === operation || 'var' === operation) {
1439
+ if (count < 2) return 'DIV/0!';
1440
+ } else if (0 === count) return 'DIV/0!';
1441
+ const mean = sum / count;
1442
+ const divisor = 'stddev' === operation || 'var' === operation ? count - 1 : count;
1443
+ const variance = numbers.reduce((total, value)=>total + (value - mean) ** 2, 0) / divisor;
1444
+ return finiteParserNumber('stddev' === operation || 'stddevp' === operation ? Math.sqrt(variance) : variance);
1445
+ }
1446
+ function flattenParserValues(value) {
1447
+ if (!Array.isArray(value)) return [
1448
+ value
1449
+ ];
1450
+ return value.flatMap((entry)=>flattenParserValues(entry));
1451
+ }
1452
+ function parserNumericValue(value) {
1453
+ if ('number' == typeof value) return Number.isFinite(value) ? value : void 0;
1454
+ if ('boolean' == typeof value) return value ? 1 : 0;
1455
+ if ('string' != typeof value || !value.trim()) return;
1456
+ const parsed = Number(value);
1457
+ return Number.isFinite(parsed) ? parsed : void 0;
1458
+ }
1459
+ function parserErrorValue(value) {
1460
+ if (value instanceof Error) return value.message.startsWith('#') ? value.message.slice(1) : 'VALUE!';
1461
+ return 'string' == typeof value && value.startsWith('#') ? value.slice(1) : void 0;
1462
+ }
1463
+ function finiteParserNumber(value) {
1464
+ return Number.isFinite(value) ? value : 'NUM!';
1465
+ }
1375
1466
  function normalizeFormulaForFortuneParser(formula) {
1376
1467
  const source = formula.replace(/^=/, '');
1377
1468
  let output = '';
@@ -1462,6 +1553,7 @@ function validateSpreadsheetCalculationRequest(request) {
1462
1553
  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.');
1463
1554
  sheetIds.add(sheet.id);
1464
1555
  sheetNames.add(normalizedName);
1556
+ validateSpreadsheetTables(sheet, request.sheets);
1465
1557
  const coordinates = new Set();
1466
1558
  for (const cell of sheet.cells){
1467
1559
  const key = `${cell.row}:${cell.column}`;
@@ -1473,6 +1565,68 @@ function validateSpreadsheetCalculationRequest(request) {
1473
1565
  }
1474
1566
  for (const target of request.targets ?? [])if (!sheetIds.has(target.sheetId) || !boundedSpreadsheetIndex(target.row, office_kernel_spreadsheet_protocol_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.');
1475
1567
  }
1568
+ function validateSpreadsheetTables(sheet, sheets) {
1569
+ const tables = sheet.tables ?? [];
1570
+ const tableCount = sheets.reduce((count, candidate)=>count + (candidate.tables?.length ?? 0), 0);
1571
+ 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.');
1572
+ const aliases = new Map();
1573
+ for (const candidate of sheets)for (const [tableIndex, table] of (candidate.tables ?? []).entries()){
1574
+ const identity = `${candidate.id}\u0000${tableIndex}`;
1575
+ for (const alias of [
1576
+ table.name,
1577
+ table.displayName
1578
+ ]){
1579
+ if (!alias) continue;
1580
+ const normalized = alias.toLocaleLowerCase();
1581
+ const existing = aliases.get(normalized);
1582
+ if (existing && existing !== identity) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet table name '${alias}' is ambiguous.`);
1583
+ aliases.set(normalized, identity);
1584
+ }
1585
+ }
1586
+ const ranges = [];
1587
+ for (const table of tables){
1588
+ for (const [kind, value] of [
1589
+ [
1590
+ 'startRow',
1591
+ table.startRow
1592
+ ],
1593
+ [
1594
+ 'endRow',
1595
+ table.endRow
1596
+ ],
1597
+ [
1598
+ 'startColumn',
1599
+ table.startColumn
1600
+ ],
1601
+ [
1602
+ 'endColumn',
1603
+ table.endColumn
1604
+ ]
1605
+ ])if (!boundedSpreadsheetIndex(value, kind.endsWith('Row') ? office_kernel_spreadsheet_protocol_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}.`);
1606
+ 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.`);
1607
+ validateTableName(table.name, 'name');
1608
+ if (void 0 !== table.displayName) validateTableName(table.displayName, 'displayName');
1609
+ const columnNames = new Set();
1610
+ for (const column of table.columns){
1611
+ validateTableName(column, 'column');
1612
+ const normalized = column.toLocaleLowerCase();
1613
+ if (columnNames.has(normalized)) throw office_kernel_spreadsheet_fallback_validation_kernelError('office.kernel.spreadsheet.table_invalid', `Spreadsheet table '${table.name}' contains duplicate column names.`);
1614
+ columnNames.add(normalized);
1615
+ }
1616
+ 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.`);
1617
+ 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.`);
1618
+ ranges.push({
1619
+ startRow: table.startRow,
1620
+ endRow: table.endRow,
1621
+ startColumn: table.startColumn,
1622
+ endColumn: table.endColumn,
1623
+ name: table.name
1624
+ });
1625
+ }
1626
+ }
1627
+ function validateTableName(value, kind) {
1628
+ 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.`);
1629
+ }
1476
1630
  function boundedSpreadsheetIndex(value, exclusiveMaximum) {
1477
1631
  return Number.isSafeInteger(value) && value >= 0 && value < exclusiveMaximum;
1478
1632
  }
@@ -1566,6 +1720,428 @@ function isParserErrorValue(value) {
1566
1720
  const withSuffix = '#DIV/0' === normalized ? '#DIV/0!' : normalized;
1567
1721
  return isOfficeKernelSpreadsheetError(withSuffix);
1568
1722
  }
1723
+ class SpreadsheetStructuredReferenceError extends Error {
1724
+ kind;
1725
+ constructor(kind, message){
1726
+ super(message);
1727
+ this.name = 'SpreadsheetStructuredReferenceError';
1728
+ this.kind = kind;
1729
+ }
1730
+ }
1731
+ function parseSpreadsheetStructuredReference(reference) {
1732
+ const open = reference.indexOf('[');
1733
+ if (open < 0) throw invalidReference(reference);
1734
+ const tableName = reference.slice(0, open) || void 0;
1735
+ const content = outerGroup(reference.slice(open));
1736
+ if (null === content) throw invalidReference(reference);
1737
+ const rows = {
1738
+ all: false,
1739
+ headers: false,
1740
+ data: false,
1741
+ totals: false,
1742
+ current: false
1743
+ };
1744
+ let firstColumn;
1745
+ let lastColumn;
1746
+ if (content.startsWith('@')) {
1747
+ rows.current = true;
1748
+ const column = parseCurrentColumn(content.slice(1), reference);
1749
+ firstColumn = column;
1750
+ lastColumn = column;
1751
+ } else if (content.startsWith('[')) [firstColumn, lastColumn] = parseNestedSelection(content, reference, rows);
1752
+ else {
1753
+ const item = tableItem(content);
1754
+ if (item) applyTableItem(rows, item, reference);
1755
+ else {
1756
+ const column = parsePlainColumn(content, reference);
1757
+ firstColumn = column;
1758
+ lastColumn = column;
1759
+ }
1760
+ }
1761
+ if (!rows.all && !rows.headers && !rows.data && !rows.totals && !rows.current) rows.data = true;
1762
+ return {
1763
+ tableName,
1764
+ firstColumn,
1765
+ lastColumn,
1766
+ rows
1767
+ };
1768
+ }
1769
+ class SpreadsheetStructuredReferenceCatalog {
1770
+ sheets;
1771
+ definitions = [];
1772
+ byName = new Map();
1773
+ bySheet = new Map();
1774
+ constructor(sheets){
1775
+ this.sheets = sheets;
1776
+ for (const sheet of sheets){
1777
+ const indexes = [];
1778
+ for (const table of sheet.tables ?? []){
1779
+ const definition = {
1780
+ ...table,
1781
+ sheetId: sheet.id,
1782
+ sheetName: sheet.name
1783
+ };
1784
+ const index = this.definitions.length;
1785
+ this.definitions.push(definition);
1786
+ indexes.push(index);
1787
+ for (const alias of [
1788
+ table.name,
1789
+ table.displayName
1790
+ ]){
1791
+ if (!alias) continue;
1792
+ const key = alias.toLocaleLowerCase();
1793
+ if (!this.byName.has(key)) this.byName.set(key, index);
1794
+ }
1795
+ }
1796
+ this.bySheet.set(sheet.id, indexes);
1797
+ }
1798
+ }
1799
+ resolve(qualifier, reference, currentSheet, currentColumn, currentRow) {
1800
+ const parsed = parseSpreadsheetStructuredReference(reference);
1801
+ const table = this.resolveTable(parsed, reference, currentSheet, currentColumn, currentRow);
1802
+ if (qualifier && table.sheetName.toLocaleLowerCase() !== qualifier.toLocaleLowerCase()) throw new SpreadsheetStructuredReferenceError('missing-table', `Spreadsheet table '${table.name}' is not on worksheet '${qualifier}'.`);
1803
+ 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}'.`);
1804
+ const [startColumn, endColumn] = this.resolveColumns(table, parsed);
1805
+ return this.resolveRows(table, parsed.rows, currentRow).map(([startRow, endRow])=>({
1806
+ sheetId: table.sheetId,
1807
+ sheetName: table.sheetName,
1808
+ startRow,
1809
+ endRow,
1810
+ startColumn,
1811
+ endColumn
1812
+ }));
1813
+ }
1814
+ resolveTable(parsed, reference, currentSheet, currentColumn, currentRow) {
1815
+ if (parsed.tableName) {
1816
+ const index = this.byName.get(parsed.tableName.toLocaleLowerCase());
1817
+ if (void 0 === index) throw new SpreadsheetStructuredReferenceError('missing-table', `Spreadsheet table '${parsed.tableName}' does not exist.`);
1818
+ const table = this.definitions[index];
1819
+ if (!table) throw invalidReference(reference);
1820
+ return table;
1821
+ }
1822
+ 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);
1823
+ if (!matching.length) throw new SpreadsheetStructuredReferenceError('missing-table', `Table-local structured reference '${reference}' requires the current formula cell to be inside a Spreadsheet table.`);
1824
+ if (matching.length > 1) throw new SpreadsheetStructuredReferenceError('unsupported', `Table-local structured reference '${reference}' is ambiguous at the current formula cell.`);
1825
+ return matching[0];
1826
+ }
1827
+ resolveColumns(table, parsed) {
1828
+ let first = 0;
1829
+ let last = table.columns.length - 1;
1830
+ if (void 0 !== parsed.firstColumn && void 0 !== parsed.lastColumn) {
1831
+ first = table.columns.findIndex((column)=>column.toLocaleLowerCase() === parsed.firstColumn.toLocaleLowerCase());
1832
+ last = table.columns.findIndex((column)=>column.toLocaleLowerCase() === parsed.lastColumn.toLocaleLowerCase());
1833
+ if (first < 0) throw new SpreadsheetStructuredReferenceError('missing-column', `Spreadsheet table '${table.name}' has no column '${parsed.firstColumn}'.`);
1834
+ if (last < 0) throw new SpreadsheetStructuredReferenceError('missing-column', `Spreadsheet table '${table.name}' has no column '${parsed.lastColumn}'.`);
1835
+ if (first > last) throw new SpreadsheetStructuredReferenceError('unsupported', `Structured-reference column range '${parsed.firstColumn}:${parsed.lastColumn}' is reversed.`);
1836
+ }
1837
+ if (first < 0 || last < first || table.startColumn + last > table.endColumn) throw invalidReference(table.name);
1838
+ return [
1839
+ table.startColumn + first,
1840
+ table.startColumn + last
1841
+ ];
1842
+ }
1843
+ resolveRows(table, rows, currentRow) {
1844
+ const selected = [];
1845
+ if (rows.all) selected.push([
1846
+ table.startRow,
1847
+ table.endRow
1848
+ ]);
1849
+ if (rows.headers) {
1850
+ if (!table.headerRow) throw missingRows(table, '#Headers');
1851
+ selected.push([
1852
+ table.startRow,
1853
+ table.startRow
1854
+ ]);
1855
+ }
1856
+ if (rows.data) selected.push(this.dataRows(table));
1857
+ if (rows.totals) {
1858
+ if (!table.totalsRow) throw missingRows(table, '#Totals');
1859
+ selected.push([
1860
+ table.endRow,
1861
+ table.endRow
1862
+ ]);
1863
+ }
1864
+ if (rows.current) {
1865
+ const [start, end] = this.dataRows(table);
1866
+ if (currentRow < start || currentRow > end) throw new SpreadsheetStructuredReferenceError('unsupported', `Structured reference #This Row requires the current formula row to be inside table '${table.name}'.`);
1867
+ selected.push([
1868
+ currentRow,
1869
+ currentRow
1870
+ ]);
1871
+ }
1872
+ selected.sort((left, right)=>left[0] - right[0]);
1873
+ const merged = [];
1874
+ for (const [start, end] of selected){
1875
+ const previous = merged.at(-1);
1876
+ if (previous && start <= previous[1] + 1) previous[1] = Math.max(previous[1], end);
1877
+ else merged.push([
1878
+ start,
1879
+ end
1880
+ ]);
1881
+ }
1882
+ if (!merged.length) throw new SpreadsheetStructuredReferenceError('unsupported', 'Structured reference selects no table rows.');
1883
+ return merged;
1884
+ }
1885
+ dataRows(table) {
1886
+ const start = table.startRow + (table.headerRow ? 1 : 0);
1887
+ const end = table.endRow - (table.totalsRow ? 1 : 0);
1888
+ if (start > end) throw new SpreadsheetStructuredReferenceError('unsupported', `Spreadsheet table '${table.name}' has no data rows.`);
1889
+ return [
1890
+ start,
1891
+ end
1892
+ ];
1893
+ }
1894
+ }
1895
+ function expandSpreadsheetStructuredReferences(formula, catalog, currentSheet, currentRow, currentColumn) {
1896
+ const hasEquals = formula.startsWith('=');
1897
+ const source = hasEquals ? formula.slice(1) : formula;
1898
+ let output = '';
1899
+ let cursor = 0;
1900
+ while(cursor < source.length){
1901
+ const character = source[cursor] ?? '';
1902
+ if ('"' === character) {
1903
+ const end = quotedStringEnd(source, cursor);
1904
+ output += source.slice(cursor, end);
1905
+ cursor = end;
1906
+ continue;
1907
+ }
1908
+ const token = scanStructuredReference(source, cursor);
1909
+ if (!token) {
1910
+ output += character;
1911
+ cursor += 1;
1912
+ continue;
1913
+ }
1914
+ const areas = catalog.resolve(token.qualifier, token.reference, currentSheet, currentColumn, currentRow);
1915
+ if (areas.length > 1) throw new SpreadsheetStructuredReferenceError('unsupported', `Structured reference '${token.reference}' resolves to disjoint row areas; the JavaScript fallback requires a contiguous range.`);
1916
+ const ranges = areas.map((area)=>{
1917
+ const prefix = area.sheetId === currentSheet.id ? '' : `${quoteSheetName(area.sheetName)}!`;
1918
+ const start = cellAddress(area.startRow, area.startColumn);
1919
+ const end = cellAddress(area.endRow, area.endColumn);
1920
+ return `${prefix}${start}${start === end ? '' : `:${end}`}`;
1921
+ });
1922
+ output += 1 === ranges.length ? ranges[0] : `(${ranges.join(',')})`;
1923
+ cursor = token.end;
1924
+ }
1925
+ return hasEquals ? `=${output}` : output;
1926
+ }
1927
+ function scanStructuredReference(source, start) {
1928
+ const qualified = scanQualifier(source, start);
1929
+ let cursor = qualified?.end ?? start;
1930
+ const qualifier = qualified?.name;
1931
+ if ('[' === source[cursor]) {
1932
+ const end = matchingBracket(source, cursor);
1933
+ if (null === end) return null;
1934
+ const content = source.slice(cursor + 1, end - 1);
1935
+ if (!content.startsWith('@') && !content.startsWith('#') && !content.startsWith('[')) return null;
1936
+ return {
1937
+ end,
1938
+ qualifier,
1939
+ reference: source.slice(cursor, end)
1940
+ };
1941
+ }
1942
+ const nameStart = cursor;
1943
+ if (!isNameStart(source[cursor] ?? '')) return null;
1944
+ cursor += 1;
1945
+ while(cursor < source.length && isNameContinue(source[cursor]))cursor += 1;
1946
+ if ('[' !== source[cursor]) return null;
1947
+ const end = matchingBracket(source, cursor);
1948
+ if (null === end) throw invalidReference(source.slice(nameStart, source.length));
1949
+ return {
1950
+ end,
1951
+ qualifier,
1952
+ reference: source.slice(nameStart, end)
1953
+ };
1954
+ }
1955
+ function scanQualifier(source, start) {
1956
+ if ("'" === source[start]) {
1957
+ let cursor = start + 1;
1958
+ let decoded = '';
1959
+ while(cursor < source.length){
1960
+ const character = source[cursor];
1961
+ if ("'" === character) {
1962
+ if ("'" === source[cursor + 1]) {
1963
+ decoded += "'";
1964
+ cursor += 2;
1965
+ continue;
1966
+ }
1967
+ if ('!' === source[cursor + 1]) return {
1968
+ name: decoded,
1969
+ end: cursor + 2
1970
+ };
1971
+ break;
1972
+ }
1973
+ decoded += character;
1974
+ cursor += 1;
1975
+ }
1976
+ return null;
1977
+ }
1978
+ let cursor = start;
1979
+ while(cursor < source.length && isQualifierCharacter(source[cursor]))cursor += 1;
1980
+ if ('!' !== source[cursor] || cursor === start) return null;
1981
+ return {
1982
+ name: source.slice(start, cursor),
1983
+ end: cursor + 1
1984
+ };
1985
+ }
1986
+ function matchingBracket(source, start) {
1987
+ let depth = 0;
1988
+ for(let cursor = start; cursor < source.length; cursor += 1){
1989
+ const character = source[cursor];
1990
+ if ("'" === character) {
1991
+ cursor += "'" === source[cursor + 1] ? 1 : 0;
1992
+ continue;
1993
+ }
1994
+ if ('[' === character) depth += 1;
1995
+ else if (']' === character) {
1996
+ depth -= 1;
1997
+ if (0 === depth) return cursor + 1;
1998
+ if (depth < 0) break;
1999
+ }
2000
+ }
2001
+ return null;
2002
+ }
2003
+ function outerGroup(value) {
2004
+ if (!value.startsWith('[')) return null;
2005
+ const end = matchingBracket(value, 0);
2006
+ return end === value.length ? value.slice(1, -1) : null;
2007
+ }
2008
+ function bracketAtom(value) {
2009
+ if (!value.startsWith('[')) return null;
2010
+ const end = matchingBracket(value, 0);
2011
+ return null === end ? null : {
2012
+ atom: value.slice(1, end - 1),
2013
+ consumed: end
2014
+ };
2015
+ }
2016
+ function parseCurrentColumn(value, reference) {
2017
+ if (value.startsWith('[')) {
2018
+ const atom = bracketAtom(value);
2019
+ if (!atom || atom.consumed !== value.length) throw invalidReference(reference);
2020
+ const column = decodeAtom(atom.atom);
2021
+ if (!column) throw invalidReference(reference);
2022
+ return column;
2023
+ }
2024
+ return parsePlainColumn(value, reference);
2025
+ }
2026
+ function parseNestedSelection(content, reference, rows) {
2027
+ const atoms = [];
2028
+ const separators = [];
2029
+ let cursor = 0;
2030
+ while(cursor < content.length){
2031
+ const atom = bracketAtom(content.slice(cursor));
2032
+ if (!atom) throw invalidReference(reference);
2033
+ atoms.push(atom.atom);
2034
+ cursor += atom.consumed;
2035
+ if (cursor === content.length) break;
2036
+ const separator = content[cursor];
2037
+ if (',' !== separator && ':' !== separator) throw invalidReference(reference);
2038
+ separators.push(separator);
2039
+ cursor += 1;
2040
+ }
2041
+ const columns = [];
2042
+ atoms.forEach((atom, index)=>{
2043
+ const item = tableItem(atom);
2044
+ if (item) applyTableItem(rows, item, reference);
2045
+ else {
2046
+ const column = decodeAtom(atom);
2047
+ if (!column) throw invalidReference(reference);
2048
+ columns.push({
2049
+ index,
2050
+ name: column
2051
+ });
2052
+ }
2053
+ });
2054
+ if (0 === columns.length) {
2055
+ if (separators.includes(':')) throw invalidReference(reference);
2056
+ return [
2057
+ void 0,
2058
+ void 0
2059
+ ];
2060
+ }
2061
+ if (1 === columns.length) {
2062
+ if (separators.includes(':')) throw invalidReference(reference);
2063
+ return [
2064
+ columns[0].name,
2065
+ columns[0].name
2066
+ ];
2067
+ }
2068
+ const first = columns[0];
2069
+ const last = columns[1];
2070
+ if (2 === columns.length && last.index === first.index + 1 && ':' === separators[first.index] && separators.every((separator, index)=>index === first.index || ',' === separator)) return [
2071
+ first.name,
2072
+ last.name
2073
+ ];
2074
+ throw new SpreadsheetStructuredReferenceError('unsupported', 'Disjoint structured-reference columns are not supported.');
2075
+ }
2076
+ function tableItem(value) {
2077
+ const normalized = value.toLocaleLowerCase();
2078
+ if ('#all' === normalized) return 'all';
2079
+ if ('#headers' === normalized) return 'headers';
2080
+ if ('#data' === normalized) return 'data';
2081
+ if ('#totals' === normalized) return 'totals';
2082
+ if ('#this row' === normalized) return 'current';
2083
+ }
2084
+ function applyTableItem(rows, item, reference) {
2085
+ rows['current' === item ? 'current' : item] = true;
2086
+ 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.`);
2087
+ }
2088
+ function parsePlainColumn(value, reference) {
2089
+ if (!value || /[[\],:]/u.test(value)) throw invalidReference(reference);
2090
+ const column = decodeAtom(value);
2091
+ if (!column) throw invalidReference(reference);
2092
+ return column;
2093
+ }
2094
+ function decodeAtom(value) {
2095
+ let output = '';
2096
+ for(let cursor = 0; cursor < value.length; cursor += 1){
2097
+ const character = value[cursor];
2098
+ if ("'" === character) {
2099
+ const escaped = value[cursor + 1];
2100
+ if (void 0 === escaped) throw invalidReference(value);
2101
+ output += escaped;
2102
+ cursor += 1;
2103
+ } else output += character;
2104
+ }
2105
+ return output;
2106
+ }
2107
+ function missingRows(table, item) {
2108
+ return new SpreadsheetStructuredReferenceError('unsupported', `Structured reference ${item} requires table '${table.name}' to contain that row.`);
2109
+ }
2110
+ function invalidReference(reference) {
2111
+ return new SpreadsheetStructuredReferenceError('invalid', `Structured reference '${reference}' is not in a supported canonical form.`);
2112
+ }
2113
+ function quotedStringEnd(source, start) {
2114
+ for(let cursor = start + 1; cursor < source.length; cursor += 1)if ('"' === source[cursor]) {
2115
+ if ('"' === source[cursor + 1]) {
2116
+ cursor += 1;
2117
+ continue;
2118
+ }
2119
+ return cursor + 1;
2120
+ }
2121
+ return source.length;
2122
+ }
2123
+ function cellAddress(row, column) {
2124
+ let value = column + 1;
2125
+ let label = '';
2126
+ while(value > 0){
2127
+ value -= 1;
2128
+ label = String.fromCharCode(65 + value % 26) + label;
2129
+ value = Math.floor(value / 26);
2130
+ }
2131
+ return `${label}${row + 1}`;
2132
+ }
2133
+ function quoteSheetName(name) {
2134
+ return /^[A-Za-z_][A-Za-z0-9_.]*$/u.test(name) ? name : `'${name.replaceAll("'", "''")}'`;
2135
+ }
2136
+ function isNameStart(value) {
2137
+ return /^[A-Za-z_\\?]$/u.test(value);
2138
+ }
2139
+ function isNameContinue(value) {
2140
+ return /^[A-Za-z0-9_.\\?]$/u.test(value);
2141
+ }
2142
+ function isQualifierCharacter(value) {
2143
+ return /^[A-Za-z0-9_.\\?[\]:$]$/u.test(value);
2144
+ }
1569
2145
  async function calculateSpreadsheetInJavaScript(request) {
1570
2146
  validateSpreadsheetCalculationRequest(request);
1571
2147
  const formulaParser = await import("@fortune-sheet/formula-parser");
@@ -1581,9 +2157,11 @@ class JavaScriptSpreadsheetEvaluator {
1581
2157
  stack = [];
1582
2158
  calculationOrder = [];
1583
2159
  issues = [];
2160
+ tableCatalog;
1584
2161
  constructor(request, formulaParser){
1585
2162
  this.request = request;
1586
2163
  this.formulaParser = formulaParser;
2164
+ this.tableCatalog = new SpreadsheetStructuredReferenceCatalog(request.sheets);
1587
2165
  for (const sheet of request.sheets){
1588
2166
  this.sheetsByName.set(sheet.name.toLowerCase(), sheet);
1589
2167
  for (const cell of sheet.cells)this.cells.set(cellKey(sheet.id, cell.row, cell.column), {
@@ -1654,16 +2232,24 @@ class JavaScriptSpreadsheetEvaluator {
1654
2232
  }
1655
2233
  evaluateFormula(coordinate, indexed) {
1656
2234
  const formula = indexed.cell.formula ?? '';
2235
+ let expandedFormula;
2236
+ try {
2237
+ expandedFormula = expandSpreadsheetStructuredReferences(formula, this.tableCatalog, indexed.sheet, coordinate.row, coordinate.column);
2238
+ } catch (error) {
2239
+ const message = error instanceof SpreadsheetStructuredReferenceError ? error.message : 'Structured reference expansion failed.';
2240
+ return failedEvaluation(indexed.cell.value, calculationIssue(coordinate, 'office.kernel.spreadsheet.formula_unsupported', message));
2241
+ }
1657
2242
  const parser = new this.formulaParser.Parser();
1658
2243
  let unresolvedDependency = false;
1659
2244
  let unsupportedReference = false;
1660
2245
  let unsupportedFunction;
1661
2246
  let materializedRangeCells = 0;
1662
2247
  parser.setFunction('IFERROR', evaluateParserIfError).setFunction('ROW', (parameters)=>parameters.length ? null : coordinate.row + 1).setFunction('COLUMN', (parameters)=>parameters.length ? null : coordinate.column + 1);
1663
- parser.on('callFunction', (name, parameters)=>{
2248
+ parser.on('callFunction', (name, parameters, done)=>{
1664
2249
  const normalized = normalizeSpreadsheetFunctionName(name);
1665
2250
  const arity = browserScalarFunctionArities.get(normalized);
1666
2251
  if (!arity || parameters.length < arity[0] || parameters.length > arity[1]) unsupportedFunction ??= name.toUpperCase();
2252
+ if ('SUBTOTAL' === normalized) done(evaluateParserSubtotal(parameters));
1667
2253
  });
1668
2254
  parser.on('callCellValue', (cell, _options, done)=>{
1669
2255
  const dependency = this.resolveCoordinate(indexed.sheet, cell);
@@ -1687,7 +2273,7 @@ class JavaScriptSpreadsheetEvaluator {
1687
2273
  return true;
1688
2274
  }));
1689
2275
  });
1690
- const parsed = parser.parse(normalizeFormulaForFortuneParser(formula), {
2276
+ const parsed = parser.parse(normalizeFormulaForFortuneParser(expandedFormula), {
1691
2277
  sheetId: indexed.sheet.id
1692
2278
  });
1693
2279
  if (unsupportedFunction) return failedEvaluation(indexed.cell.value, calculationIssue(coordinate, 'office.kernel.spreadsheet.formula_unsupported', `Formula function '${unsupportedFunction}' is not supported.`));
@@ -1711,7 +2297,8 @@ class JavaScriptSpreadsheetEvaluator {
1711
2297
  }
1712
2298
  rangeValues(currentSheet, start, end, onUnresolvedDependency, onUnsupportedReference, reserveRangeCells) {
1713
2299
  const startCoordinate = this.resolveCoordinate(currentSheet, start);
1714
- const endCoordinate = this.resolveCoordinate(currentSheet, end);
2300
+ const rangeSheet = start.sheetName ? this.sheetsByName.get(start.sheetName.toLowerCase()) : currentSheet;
2301
+ const endCoordinate = rangeSheet ? this.resolveCoordinate(rangeSheet, end) : null;
1715
2302
  if (!startCoordinate || !endCoordinate || startCoordinate.sheetId !== endCoordinate.sheetId) {
1716
2303
  onUnsupportedReference();
1717
2304
  return [];