@bilig/xlsx 0.164.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.
Files changed (83) hide show
  1. package/README.md +25 -0
  2. package/dist/address.d.ts +15 -0
  3. package/dist/address.js +82 -0
  4. package/dist/address.js.map +1 -0
  5. package/dist/external-workbook-types.d.ts +44 -0
  6. package/dist/external-workbook-types.js +12 -0
  7. package/dist/external-workbook-types.js.map +1 -0
  8. package/dist/file-source.d.ts +10 -0
  9. package/dist/file-source.js +76 -0
  10. package/dist/file-source.js.map +1 -0
  11. package/dist/formula-cache-reader.d.ts +30 -0
  12. package/dist/formula-cache-reader.js +350 -0
  13. package/dist/formula-cache-reader.js.map +1 -0
  14. package/dist/index.d.ts +17 -0
  15. package/dist/index.js +18 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/simple-workbook-writer.d.ts +114 -0
  18. package/dist/simple-workbook-writer.js +621 -0
  19. package/dist/simple-workbook-writer.js.map +1 -0
  20. package/dist/source-preserving-literal-patches.d.ts +43 -0
  21. package/dist/source-preserving-literal-patches.js +615 -0
  22. package/dist/source-preserving-literal-patches.js.map +1 -0
  23. package/dist/source-preserving-zip.d.ts +25 -0
  24. package/dist/source-preserving-zip.js +341 -0
  25. package/dist/source-preserving-zip.js.map +1 -0
  26. package/dist/streaming-native-cell-arena.d.ts +57 -0
  27. package/dist/streaming-native-cell-arena.js +233 -0
  28. package/dist/streaming-native-cell-arena.js.map +1 -0
  29. package/dist/streaming-native-external-cache.d.ts +13 -0
  30. package/dist/streaming-native-external-cache.js +753 -0
  31. package/dist/streaming-native-external-cache.js.map +1 -0
  32. package/dist/streaming-native-inspect.d.ts +42 -0
  33. package/dist/streaming-native-inspect.js +297 -0
  34. package/dist/streaming-native-inspect.js.map +1 -0
  35. package/dist/streaming-native-lookup-wasm.d.ts +13 -0
  36. package/dist/streaming-native-lookup-wasm.js +250 -0
  37. package/dist/streaming-native-lookup-wasm.js.map +1 -0
  38. package/dist/streaming-native-recalc-evaluator.d.ts +17 -0
  39. package/dist/streaming-native-recalc-evaluator.js +743 -0
  40. package/dist/streaming-native-recalc-evaluator.js.map +1 -0
  41. package/dist/streaming-native-recalc.d.ts +97 -0
  42. package/dist/streaming-native-recalc.js +652 -0
  43. package/dist/streaming-native-recalc.js.map +1 -0
  44. package/dist/streaming-native-row-chain-conditionals.d.ts +8 -0
  45. package/dist/streaming-native-row-chain-conditionals.js +385 -0
  46. package/dist/streaming-native-row-chain-conditionals.js.map +1 -0
  47. package/dist/streaming-native-row-chain-dependencies.d.ts +17 -0
  48. package/dist/streaming-native-row-chain-dependencies.js +365 -0
  49. package/dist/streaming-native-row-chain-dependencies.js.map +1 -0
  50. package/dist/streaming-native-row-chain-references.d.ts +3 -0
  51. package/dist/streaming-native-row-chain-references.js +36 -0
  52. package/dist/streaming-native-row-chain-references.js.map +1 -0
  53. package/dist/streaming-native-row-chain-wasm.d.ts +56 -0
  54. package/dist/streaming-native-row-chain-wasm.js +546 -0
  55. package/dist/streaming-native-row-chain-wasm.js.map +1 -0
  56. package/dist/streaming-native-text.d.ts +2 -0
  57. package/dist/streaming-native-text.js +14 -0
  58. package/dist/streaming-native-text.js.map +1 -0
  59. package/dist/streaming-native-workbook-core.d.ts +47 -0
  60. package/dist/streaming-native-workbook-core.js +110 -0
  61. package/dist/streaming-native-workbook-core.js.map +1 -0
  62. package/dist/targeted-cell-reader.d.ts +11 -0
  63. package/dist/targeted-cell-reader.js +92 -0
  64. package/dist/targeted-cell-reader.js.map +1 -0
  65. package/dist/workbook-cell-reader.d.ts +29 -0
  66. package/dist/workbook-cell-reader.js +200 -0
  67. package/dist/workbook-cell-reader.js.map +1 -0
  68. package/dist/workbook-compatibility-report.d.ts +101 -0
  69. package/dist/workbook-compatibility-report.js +654 -0
  70. package/dist/workbook-compatibility-report.js.map +1 -0
  71. package/dist/workbook-sheet-paths.d.ts +8 -0
  72. package/dist/workbook-sheet-paths.js +79 -0
  73. package/dist/workbook-sheet-paths.js.map +1 -0
  74. package/dist/xml-part-patch.d.ts +12 -0
  75. package/dist/xml-part-patch.js +45 -0
  76. package/dist/xml-part-patch.js.map +1 -0
  77. package/dist/xml.d.ts +9 -0
  78. package/dist/xml.js +42 -0
  79. package/dist/xml.js.map +1 -0
  80. package/dist/zip-reader.d.ts +51 -0
  81. package/dist/zip-reader.js +448 -0
  82. package/dist/zip-reader.js.map +1 -0
  83. package/package.json +113 -0
@@ -0,0 +1,652 @@
1
+ import { ErrorCode, ValueTag } from '@bilig/protocol';
2
+ import { decodeCellAddress, decodeCellRange, encodeCellAddress } from './address.js';
3
+ import { exportXlsxSourceLiteralPatchesToFileAsync } from './source-preserving-literal-patches.js';
4
+ import { normalizeExternalWorkbookReferences, readStreamingNativeExternalCachedRowsByAlias } from './streaming-native-external-cache.js';
5
+ import { StreamingNativeSheetCellArena, } from './streaming-native-cell-arena.js';
6
+ import { expandStreamingNativeFormulaDependencyRows } from './streaming-native-row-chain-wasm.js';
7
+ import { closeStreamingNativeWorkbookCore, openStreamingNativeWorkbookCore, StreamingNativeWorkbookOpenError, } from './streaming-native-workbook-core.js';
8
+ import { decodeXmlText, readXmlAttribute, worksheetCellElementPattern, worksheetCellOpeningTagPattern } from './xml.js';
9
+ import { evaluateFormulaCells, readTargets, resolveFormulaSource } from './streaming-native-recalc-evaluator.js';
10
+ import { normalizeStructuredReferenceColumnName } from './streaming-native-text.js';
11
+ import { forEachInflatedXlsxZipEntryChunk, getZipText, normalizeZipPath } from './zip-reader.js';
12
+ export class StreamingNativeXlsxRecalcError extends Error {
13
+ diagnostics;
14
+ constructor(message, diagnostics) {
15
+ super(message);
16
+ this.name = 'StreamingNativeXlsxRecalcError';
17
+ this.diagnostics = diagnostics;
18
+ }
19
+ }
20
+ const emptyCellValue = Object.freeze({ tag: ValueTag.Empty });
21
+ const textDecoder = new TextDecoder();
22
+ const formulaElementPattern = /<((?:[A-Za-z_][\w.-]*:)?f)\b(?:[^>"']|"[^"]*"|'[^']*')*(?:\/>|>[\s\S]*?<\/\1>)/u;
23
+ const tablePartPattern = /<(?:[A-Za-z_][\w.-]*:)?tablePart\b(?:[^>"']|"[^"]*"|'[^']*')*\/?>/gu;
24
+ const relationshipPattern = /<Relationship\b(?:[^>"']|"[^"]*"|'[^']*')*\/?>/gu;
25
+ const tableRelationshipType = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table';
26
+ export async function recalculateXlsxFileToFileStreamingNative(inputPath, options) {
27
+ if (!options.dryRun && options.outputPath.length === 0) {
28
+ throw new Error('streaming-native outputPath is required unless dryRun is enabled');
29
+ }
30
+ let inputBytes = 0;
31
+ const phaseRssPeaks = [];
32
+ let maxObservedRssBytes = 0;
33
+ const recordPhase = (phase) => {
34
+ const rssBytes = process.memoryUsage().rss;
35
+ maxObservedRssBytes = Math.max(maxObservedRssBytes, rssBytes);
36
+ phaseRssPeaks.push({ phase, rssBytes });
37
+ if (options.maxRssBytes !== undefined && rssBytes > options.maxRssBytes) {
38
+ throw streamingError(`streaming-native exceeded maxRssBytes during ${phase}`, phaseRssPeaks, {
39
+ inputBytes,
40
+ maxObservedRssBytes,
41
+ maxRssBytes: options.maxRssBytes,
42
+ });
43
+ }
44
+ };
45
+ let core = null;
46
+ try {
47
+ core = openStreamingNativeWorkbookCore(inputPath, {
48
+ onPhase: (phase, info) => {
49
+ inputBytes = info.inputBytes;
50
+ recordPhase(phase);
51
+ },
52
+ });
53
+ inputBytes = core.inputBytes;
54
+ const { source, zip, sheetEntries, sheetNames } = core;
55
+ const sheetPathsByName = new Map(sheetEntries.map((sheet) => [sheet.name, sheet.path]));
56
+ const edits = (options.edits ?? []).map((edit) => Object.assign(parseQualifiedTarget(edit.target), { value: edit.value }));
57
+ const reads = (options.reads ?? []).map(parseQualifiedTarget);
58
+ validateTargets([...edits, ...reads], sheetPathsByName);
59
+ const targetRowsBySheet = targetRowsForTargets([...edits, ...reads]);
60
+ const sheetScans = new Map();
61
+ for (const sheet of sheetEntries) {
62
+ const targetRows = targetRowsBySheet.get(sheet.name);
63
+ if (!targetRows || targetRows.size === 0) {
64
+ continue;
65
+ }
66
+ const scan = scanWorksheet(zip, sheet.name, sheet.path, targetRows);
67
+ sheetScans.set(sheet.name, scan);
68
+ }
69
+ recordPhase('worksheet-scan');
70
+ const resolveParserFormulaSource = (scan, cell) => normalizeExternalWorkbookReferences(resolveFormulaSource(scan, cell));
71
+ const expandedDependencySheets = expandStreamingNativeFormulaDependencyRows({
72
+ sheetScans,
73
+ resolveFormulaSource: resolveParserFormulaSource,
74
+ targetRowsForSheet: (sheetName) => {
75
+ if (!sheetPathsByName.has(sheetName)) {
76
+ return undefined;
77
+ }
78
+ const targetRows = targetRowsBySheet.get(sheetName) ?? new Set();
79
+ targetRowsBySheet.set(sheetName, targetRows);
80
+ return targetRows;
81
+ },
82
+ });
83
+ for (const sheetName of expandedDependencySheets) {
84
+ const sheetPath = sheetPathsByName.get(sheetName);
85
+ const targetRows = targetRowsBySheet.get(sheetName);
86
+ if (!sheetPath || !targetRows || targetRows.size === 0) {
87
+ continue;
88
+ }
89
+ const nextScan = scanWorksheet(zip, sheetName, sheetPath, targetRows);
90
+ nextScan.formulaCells.splice(0, nextScan.formulaCells.length, ...nextScan.formulaCells.filter((cell) => targetRows.has(cell.row)));
91
+ sheetScans.set(sheetName, nextScan);
92
+ }
93
+ recordPhase('dependency-row-scan');
94
+ const sharedStrings = readTargetSharedStrings(zip, collectSharedStringReferences(sheetScans));
95
+ hydrateSharedStringReferences(sheetScans, sharedStrings);
96
+ recordPhase('shared-strings');
97
+ const expandedHydratedDependencySheets = expandStreamingNativeFormulaDependencyRows({
98
+ sheetScans,
99
+ resolveFormulaSource: resolveParserFormulaSource,
100
+ targetRowsForSheet: (sheetName) => {
101
+ if (!sheetPathsByName.has(sheetName)) {
102
+ return undefined;
103
+ }
104
+ const targetRows = targetRowsBySheet.get(sheetName) ?? new Set();
105
+ targetRowsBySheet.set(sheetName, targetRows);
106
+ return targetRows;
107
+ },
108
+ });
109
+ for (const sheetName of expandedHydratedDependencySheets) {
110
+ const sheetPath = sheetPathsByName.get(sheetName);
111
+ const targetRows = targetRowsBySheet.get(sheetName);
112
+ if (!sheetPath || !targetRows || targetRows.size === 0) {
113
+ continue;
114
+ }
115
+ const nextScan = scanWorksheet(zip, sheetName, sheetPath, targetRows);
116
+ nextScan.formulaCells.splice(0, nextScan.formulaCells.length, ...nextScan.formulaCells.filter((cell) => targetRows.has(cell.row)));
117
+ sheetScans.set(sheetName, nextScan);
118
+ }
119
+ if (expandedHydratedDependencySheets.size > 0) {
120
+ recordPhase('hydrated-dependency-row-scan');
121
+ const hydratedSharedStrings = readTargetSharedStrings(zip, collectSharedStringReferences(sheetScans));
122
+ hydrateSharedStringReferences(sheetScans, hydratedSharedStrings);
123
+ recordPhase('hydrated-dependency-shared-strings');
124
+ }
125
+ const tablesBySheet = readNativeTablesBySheet(zip, sheetScans);
126
+ recordPhase('table-metadata');
127
+ const externalCache = readStreamingNativeExternalCachedRowsByAlias(zip, sheetScans, resolveFormulaSource, options.externalWorkbooks);
128
+ const externalCachedRowsByAlias = externalCache.rowsByAlias;
129
+ if (externalCachedRowsByAlias.size > 0) {
130
+ recordPhase('external-link-cache');
131
+ }
132
+ const patches = [];
133
+ applyEdits(edits, sheetScans, patches);
134
+ const formulaCounts = evaluateFormulaCells(sheetScans, tablesBySheet, externalCachedRowsByAlias, patches);
135
+ const readValues = readTargets(reads, sheetScans);
136
+ const targetRowCount = [...targetRowsBySheet.values()].reduce((sum, rows) => sum + rows.size, 0);
137
+ const editCount = edits.length;
138
+ const readCount = reads.length;
139
+ const patchedSheetNames = [...new Set(patches.map((patch) => patch.sheetName))];
140
+ recordPhase('evaluate');
141
+ sheetScans.clear();
142
+ if (tablesBySheet instanceof Map) {
143
+ tablesBySheet.clear();
144
+ }
145
+ targetRowsBySheet.clear();
146
+ sheetPathsByName.clear();
147
+ const bytesWritten = options.dryRun
148
+ ? 0
149
+ : (await exportXlsxSourceLiteralPatchesToFileAsync({
150
+ source,
151
+ patches,
152
+ textPatches: externalCache.textPatches,
153
+ sheetNames: patchedSheetNames,
154
+ outputPath: options.outputPath,
155
+ })).bytesWritten;
156
+ patches.length = 0;
157
+ recordPhase(options.dryRun ? 'dry-run-output' : 'write-output');
158
+ const diagnostics = {
159
+ engineMode: 'streaming-native',
160
+ fallbackUsed: false,
161
+ inputBytes,
162
+ phaseRssPeaks,
163
+ maxObservedRssBytes,
164
+ ...(options.maxRssBytes === undefined ? {} : { maxRssBytes: options.maxRssBytes }),
165
+ sheetCount: sheetNames.length,
166
+ targetRowCount,
167
+ editCount,
168
+ readCount,
169
+ formulaCounts,
170
+ patchedCacheCount: formulaCounts.patchedFormulaCacheCount,
171
+ ...(externalCache.diagnostics === undefined ? {} : { externalWorkbookHydration: externalCache.diagnostics }),
172
+ };
173
+ return {
174
+ bytesWritten,
175
+ warnings: externalCache.warnings,
176
+ sheetNames,
177
+ reads: readValues,
178
+ changes: [],
179
+ diagnostics,
180
+ };
181
+ }
182
+ catch (error) {
183
+ if (error instanceof StreamingNativeXlsxRecalcError) {
184
+ throw error;
185
+ }
186
+ const unsupportedReason = error instanceof StreamingNativeWorkbookOpenError ? error.reason : error instanceof Error ? error.message : String(error);
187
+ const errorInputBytes = error instanceof StreamingNativeWorkbookOpenError ? error.inputBytes : inputBytes;
188
+ throw streamingError(`streaming-native could not recalculate this workbook: ${unsupportedReason}`, phaseRssPeaks, {
189
+ inputBytes: errorInputBytes,
190
+ maxObservedRssBytes,
191
+ ...(options.maxRssBytes === undefined ? {} : { maxRssBytes: options.maxRssBytes }),
192
+ unsupportedReason,
193
+ });
194
+ }
195
+ finally {
196
+ if (core) {
197
+ closeStreamingNativeWorkbookCore(core);
198
+ }
199
+ }
200
+ }
201
+ function streamingError(message, phaseRssPeaks, args) {
202
+ return new StreamingNativeXlsxRecalcError(message, {
203
+ engineMode: 'streaming-native',
204
+ fallbackUsed: false,
205
+ inputBytes: args.inputBytes,
206
+ phaseRssPeaks,
207
+ maxObservedRssBytes: args.maxObservedRssBytes,
208
+ ...(args.maxRssBytes === undefined ? {} : { maxRssBytes: args.maxRssBytes }),
209
+ sheetCount: 0,
210
+ targetRowCount: 0,
211
+ editCount: 0,
212
+ readCount: 0,
213
+ formulaCounts: {
214
+ scannedFormulaCellCount: 0,
215
+ targetedFormulaCellCount: 0,
216
+ evaluatedFormulaCellCount: 0,
217
+ patchedFormulaCacheCount: 0,
218
+ unsupportedFormulaCellCount: args.unsupportedReason ? 1 : 0,
219
+ nativeKernelFormulaCellCount: 0,
220
+ nativeKernelBatchCount: 0,
221
+ },
222
+ patchedCacheCount: 0,
223
+ ...(args.unsupportedReason === undefined ? {} : { unsupportedReason: args.unsupportedReason }),
224
+ });
225
+ }
226
+ function parseQualifiedTarget(target) {
227
+ const trimmed = target.trim();
228
+ const separator = findSheetSeparator(trimmed);
229
+ if (separator <= 0 || separator >= trimmed.length - 1) {
230
+ throw new Error(`Expected a sheet-qualified A1 target such as Data!R57152, received: ${target}`);
231
+ }
232
+ const sheetName = unquoteSheetName(trimmed.slice(0, separator));
233
+ const address = encodeCellAddress(decodeCellAddress(trimmed
234
+ .slice(separator + 1)
235
+ .replaceAll('$', '')
236
+ .toUpperCase()));
237
+ const decoded = decodeCellAddress(address);
238
+ return {
239
+ source: target,
240
+ sheetName,
241
+ address,
242
+ row: decoded.r,
243
+ col: decoded.c,
244
+ };
245
+ }
246
+ function findSheetSeparator(value) {
247
+ let quoted = false;
248
+ for (let index = 0; index < value.length; index += 1) {
249
+ const character = value[index];
250
+ if (character === "'") {
251
+ if (quoted && value[index + 1] === "'") {
252
+ index += 1;
253
+ continue;
254
+ }
255
+ quoted = !quoted;
256
+ continue;
257
+ }
258
+ if (character === '!' && !quoted) {
259
+ return index;
260
+ }
261
+ }
262
+ return -1;
263
+ }
264
+ function unquoteSheetName(value) {
265
+ if (value.startsWith("'") && value.endsWith("'")) {
266
+ return value.slice(1, -1).replaceAll("''", "'");
267
+ }
268
+ return value;
269
+ }
270
+ function validateTargets(targets, sheetPathsByName) {
271
+ for (const target of targets) {
272
+ if (!sheetPathsByName.has(target.sheetName)) {
273
+ throw new Error(`Unknown sheet in XLSX formula recalculation target: ${target.sheetName}`);
274
+ }
275
+ }
276
+ }
277
+ function targetRowsForTargets(targets) {
278
+ const output = new Map();
279
+ for (const target of targets) {
280
+ const rows = output.get(target.sheetName) ?? new Set();
281
+ rows.add(target.row);
282
+ output.set(target.sheetName, rows);
283
+ }
284
+ return output;
285
+ }
286
+ function scanWorksheet(zip, sheetName, sheetPath, targetRows) {
287
+ const state = {
288
+ sheetName,
289
+ sheetPath,
290
+ targetRows,
291
+ rows: new StreamingNativeSheetCellArena(),
292
+ formulaCells: [],
293
+ sharedFormulaMasters: new Map(),
294
+ tableRelationshipIds: new Set(),
295
+ scannedFormulaCellCount: 0,
296
+ };
297
+ let buffer = '';
298
+ const processBuffer = (final) => {
299
+ const safeEnd = final ? buffer.length : lastWorksheetCellStartIndex(buffer);
300
+ if (safeEnd === 0 && !final) {
301
+ return;
302
+ }
303
+ const safeXml = buffer.slice(0, safeEnd);
304
+ for (const match of safeXml.matchAll(new RegExp(worksheetCellElementPattern.source, 'gu'))) {
305
+ scanWorksheetCell(state, match[0]);
306
+ }
307
+ for (const match of safeXml.matchAll(tablePartPattern)) {
308
+ const id = readXmlAttribute(match[0], 'r:id') ?? readXmlAttribute(match[0], 'id');
309
+ if (id) {
310
+ state.tableRelationshipIds.add(id);
311
+ }
312
+ }
313
+ buffer = buffer.slice(safeEnd);
314
+ };
315
+ const streamed = forEachInflatedXlsxZipEntryChunk(zip, sheetPath, (chunk) => {
316
+ buffer += textDecoder.decode(chunk, { stream: true });
317
+ processBuffer(false);
318
+ }, { chunkSize: 64 * 1024, forceStreamingInflate: true });
319
+ if (!streamed) {
320
+ throw new Error(`Unable to stream worksheet XML for ${sheetName}`);
321
+ }
322
+ buffer += textDecoder.decode();
323
+ processBuffer(true);
324
+ return state;
325
+ }
326
+ function lastWorksheetCellStartIndex(xml) {
327
+ let index = -1;
328
+ for (const match of xml.matchAll(/<(?:(?:[A-Za-z_][\w.-]*):)?c\b/gu)) {
329
+ index = match.index ?? index;
330
+ }
331
+ return Math.max(0, index);
332
+ }
333
+ function scanWorksheetCell(state, cellXml) {
334
+ const openingTag = worksheetCellOpeningTagPattern.exec(cellXml)?.[0];
335
+ const addressText = openingTag ? readXmlAttribute(openingTag, 'r') : null;
336
+ if (!openingTag || !addressText) {
337
+ return;
338
+ }
339
+ let address;
340
+ try {
341
+ address = decodeCellAddress(addressText);
342
+ }
343
+ catch {
344
+ return;
345
+ }
346
+ const formula = readFormulaInfo(cellXml);
347
+ if (formula) {
348
+ state.scannedFormulaCellCount += 1;
349
+ if (formula.sharedFormulaIndex && formula.formula) {
350
+ state.sharedFormulaMasters.set(formula.sharedFormulaIndex, {
351
+ formula: formula.formula,
352
+ row: address.r,
353
+ col: address.c,
354
+ });
355
+ }
356
+ }
357
+ if (!state.targetRows.has(address.r)) {
358
+ return;
359
+ }
360
+ const row = state.rows.getOrCreate(address.r);
361
+ row.set(address.c, readCellValue(cellXml, openingTag));
362
+ if (formula) {
363
+ state.formulaCells.push({
364
+ sheetName: state.sheetName,
365
+ address: encodeCellAddress(address),
366
+ row: address.r,
367
+ col: address.c,
368
+ formula: formula.formula,
369
+ sharedFormulaIndex: formula.sharedFormulaIndex,
370
+ });
371
+ }
372
+ }
373
+ function readFormulaInfo(cellXml) {
374
+ const formulaXml = formulaElementPattern.exec(cellXml)?.[0];
375
+ if (!formulaXml) {
376
+ return null;
377
+ }
378
+ const openingEnd = formulaXml.indexOf('>');
379
+ const openingTag = openingEnd >= 0 ? formulaXml.slice(0, openingEnd + 1) : formulaXml;
380
+ const sharedFormulaIndex = readXmlAttribute(openingTag, 'si');
381
+ const sharedFormulaRef = readXmlAttribute(openingTag, 'ref');
382
+ const formula = formulaXml.endsWith('/>') || openingEnd < 0
383
+ ? null
384
+ : decodeXmlText(formulaXml.slice(openingEnd + 1, formulaXml.replace(/<\/(?:[A-Za-z_][\w.-]*:)?f>\s*$/u, '').length));
385
+ return {
386
+ formula: formula && formula.trim().length > 0 ? formula : null,
387
+ sharedFormulaIndex,
388
+ sharedFormulaRef,
389
+ };
390
+ }
391
+ function readCellValue(cellXml, openingTag) {
392
+ const type = readXmlAttribute(openingTag, 't');
393
+ if (type === 'inlineStr') {
394
+ return stringCellValue(readTextRuns(cellXml));
395
+ }
396
+ const rawValue = readElementText(cellXml, 'v');
397
+ if (rawValue === null) {
398
+ return emptyCellValue;
399
+ }
400
+ if (type === 's') {
401
+ const index = Number(rawValue);
402
+ return Number.isSafeInteger(index) && index >= 0 ? { kind: 'shared-string', index } : emptyCellValue;
403
+ }
404
+ if (type === 'str') {
405
+ return stringCellValue(decodeXmlText(rawValue));
406
+ }
407
+ if (type === 'b') {
408
+ return { tag: ValueTag.Boolean, value: rawValue === '1' || rawValue.toLowerCase() === 'true' };
409
+ }
410
+ if (type === 'e') {
411
+ return { tag: ValueTag.Error, code: errorCodeForText(decodeXmlText(rawValue)) };
412
+ }
413
+ const numeric = Number(rawValue);
414
+ return Number.isFinite(numeric) ? { tag: ValueTag.Number, value: numeric } : stringCellValue(decodeXmlText(rawValue));
415
+ }
416
+ function errorCodeForText(value) {
417
+ switch (value.toUpperCase()) {
418
+ case '#DIV/0!':
419
+ return ErrorCode.Div0;
420
+ case '#REF!':
421
+ return ErrorCode.Ref;
422
+ case '#VALUE!':
423
+ return ErrorCode.Value;
424
+ case '#NAME?':
425
+ return ErrorCode.Name;
426
+ case '#N/A':
427
+ return ErrorCode.NA;
428
+ case '#NUM!':
429
+ return ErrorCode.Num;
430
+ case '#FIELD!':
431
+ return ErrorCode.Field;
432
+ case '#NULL!':
433
+ return ErrorCode.Null;
434
+ default:
435
+ return ErrorCode.Value;
436
+ }
437
+ }
438
+ function stringCellValue(value) {
439
+ return { tag: ValueTag.String, value, stringId: 0 };
440
+ }
441
+ function collectSharedStringReferences(sheetScans) {
442
+ const indexes = new Set();
443
+ for (const scan of sheetScans.values()) {
444
+ for (const row of scan.rows.values()) {
445
+ for (const value of row.values()) {
446
+ if (isSharedStringReference(value)) {
447
+ indexes.add(value.index);
448
+ }
449
+ }
450
+ }
451
+ }
452
+ return indexes;
453
+ }
454
+ function readTargetSharedStrings(zip, targetIndexes) {
455
+ const values = new Map();
456
+ if (targetIndexes.size === 0) {
457
+ return values;
458
+ }
459
+ let buffer = '';
460
+ let index = 0;
461
+ const processBuffer = (final) => {
462
+ const safeEnd = final ? buffer.length : Math.max(0, buffer.lastIndexOf('<si'));
463
+ if (safeEnd === 0 && !final) {
464
+ return true;
465
+ }
466
+ const safeXml = buffer.slice(0, safeEnd);
467
+ for (const match of safeXml.matchAll(/<((?:[A-Za-z_][\w.-]*:)?si)\b(?:[^>"']|"[^"]*"|'[^']*')*>[\s\S]*?<\/\1>/gu)) {
468
+ if (targetIndexes.has(index)) {
469
+ values.set(index, readTextRuns(match[0]));
470
+ }
471
+ index += 1;
472
+ }
473
+ buffer = buffer.slice(safeEnd);
474
+ return values.size < targetIndexes.size;
475
+ };
476
+ const streamed = forEachInflatedXlsxZipEntryChunk(zip, 'xl/sharedStrings.xml', (chunk) => {
477
+ buffer += textDecoder.decode(chunk, { stream: true });
478
+ return processBuffer(false);
479
+ }, { chunkSize: 64 * 1024, forceStreamingInflate: true });
480
+ if (!streamed) {
481
+ return values;
482
+ }
483
+ buffer += textDecoder.decode();
484
+ processBuffer(true);
485
+ return values;
486
+ }
487
+ function hydrateSharedStringReferences(sheetScans, sharedStrings) {
488
+ for (const scan of sheetScans.values()) {
489
+ for (const row of scan.rows.values()) {
490
+ for (const [col, value] of row.entries()) {
491
+ if (!isSharedStringReference(value)) {
492
+ continue;
493
+ }
494
+ const sharedString = sharedStrings.get(value.index);
495
+ if (sharedString === undefined) {
496
+ throw new Error(`Unable to resolve shared string ${String(value.index)}`);
497
+ }
498
+ row.set(col, stringCellValue(sharedString));
499
+ }
500
+ }
501
+ }
502
+ }
503
+ function isSharedStringReference(value) {
504
+ return typeof value === 'object' && value !== null && 'kind' in value && value.kind === 'shared-string';
505
+ }
506
+ function readNativeTablesBySheet(zip, sheetScans) {
507
+ const output = new Map();
508
+ for (const scan of sheetScans.values()) {
509
+ if (scan.tableRelationshipIds.size === 0) {
510
+ continue;
511
+ }
512
+ const relationships = readWorksheetRelationships(zip, scan.sheetPath);
513
+ const tables = [];
514
+ for (const relationshipId of scan.tableRelationshipIds) {
515
+ const target = relationships.get(relationshipId);
516
+ if (!target) {
517
+ continue;
518
+ }
519
+ const tableXml = getZipText(zip, target);
520
+ const table = tableXml ? parseNativeTableXml(scan.sheetName, tableXml) : null;
521
+ if (table) {
522
+ tables.push(table);
523
+ }
524
+ }
525
+ if (tables.length > 0) {
526
+ output.set(scan.sheetName, tables);
527
+ }
528
+ }
529
+ return output;
530
+ }
531
+ function readWorksheetRelationships(zip, worksheetPath) {
532
+ const relationshipsXml = getZipText(zip, worksheetRelationshipsPath(worksheetPath));
533
+ const relationships = new Map();
534
+ if (!relationshipsXml) {
535
+ return relationships;
536
+ }
537
+ for (const match of relationshipsXml.matchAll(relationshipPattern)) {
538
+ const tag = match[0];
539
+ const id = readXmlAttribute(tag, 'Id');
540
+ const target = readXmlAttribute(tag, 'Target');
541
+ const type = readXmlAttribute(tag, 'Type');
542
+ if (id && target && type && (type === tableRelationshipType || type.endsWith('/table'))) {
543
+ relationships.set(id, resolveTargetPath(worksheetPath, target));
544
+ }
545
+ }
546
+ return relationships;
547
+ }
548
+ function worksheetRelationshipsPath(worksheetPath) {
549
+ const slash = worksheetPath.lastIndexOf('/');
550
+ const directory = slash >= 0 ? worksheetPath.slice(0, slash) : '';
551
+ const fileName = slash >= 0 ? worksheetPath.slice(slash + 1) : worksheetPath;
552
+ return `${directory}/_rels/${fileName}.rels`;
553
+ }
554
+ function resolveTargetPath(fromPath, target) {
555
+ if (target.startsWith('/')) {
556
+ return normalizeZipPath(target);
557
+ }
558
+ const baseParts = normalizeZipPath(fromPath).split('/').slice(0, -1);
559
+ for (const part of target.split('/')) {
560
+ if (part.length === 0 || part === '.') {
561
+ continue;
562
+ }
563
+ if (part === '..') {
564
+ baseParts.pop();
565
+ continue;
566
+ }
567
+ baseParts.push(part);
568
+ }
569
+ return normalizeZipPath(baseParts.join('/'));
570
+ }
571
+ function parseNativeTableXml(sheetName, tableXml) {
572
+ const tableTag = /<(?:[A-Za-z_][\w.-]*:)?table\b(?:[^>"']|"[^"]*"|'[^']*')*(?:\/>|>)/u.exec(tableXml)?.[0];
573
+ if (!tableTag) {
574
+ return null;
575
+ }
576
+ const name = readXmlAttribute(tableTag, 'name') ?? readXmlAttribute(tableTag, 'displayName');
577
+ const displayName = readXmlAttribute(tableTag, 'displayName') ?? name;
578
+ const ref = readXmlAttribute(tableTag, 'ref');
579
+ if (!name || !displayName || !ref) {
580
+ return null;
581
+ }
582
+ let range;
583
+ try {
584
+ range = decodeCellRange(ref);
585
+ }
586
+ catch {
587
+ return null;
588
+ }
589
+ const headerRowCount = parseNonNegativeInteger(readXmlAttribute(tableTag, 'headerRowCount'), 1);
590
+ const totalsRowCount = parseNonNegativeInteger(readXmlAttribute(tableTag, 'totalsRowCount'), 0);
591
+ const columns = [...tableXml.matchAll(/<(?:[A-Za-z_][\w.-]*:)?tableColumn\b(?:[^>"']|"[^"]*"|'[^']*')*(?:\/>|>)/gu)].flatMap((match) => {
592
+ const columnName = readXmlAttribute(match[0], 'name');
593
+ return columnName ? [normalizeStructuredReferenceColumnName(columnName)] : [];
594
+ });
595
+ return columns.length > 0
596
+ ? {
597
+ name,
598
+ displayName,
599
+ sheetName,
600
+ range,
601
+ headerRowCount,
602
+ totalsRowCount,
603
+ columns,
604
+ }
605
+ : null;
606
+ }
607
+ function parseNonNegativeInteger(raw, fallback) {
608
+ if (raw === null) {
609
+ return fallback;
610
+ }
611
+ const value = Number(raw);
612
+ return Number.isSafeInteger(value) && value >= 0 ? value : fallback;
613
+ }
614
+ function applyEdits(edits, sheetScans, patches) {
615
+ for (const edit of edits) {
616
+ if (typeof edit.value === 'string' && edit.value.startsWith('=')) {
617
+ throw new Error(`streaming-native does not support formula edits: ${edit.sheetName}!${edit.address}`);
618
+ }
619
+ const scan = sheetScans.get(edit.sheetName);
620
+ if (!scan) {
621
+ throw new Error(`Missing scan state for edited sheet ${edit.sheetName}`);
622
+ }
623
+ const row = scan.rows.getOrCreate(edit.row);
624
+ row.set(edit.col, cellValueFromLiteralInput(edit.value));
625
+ patches.push({
626
+ sheetName: edit.sheetName,
627
+ address: edit.address,
628
+ value: edit.value,
629
+ });
630
+ }
631
+ }
632
+ function cellValueFromLiteralInput(value) {
633
+ if (value === null) {
634
+ return emptyCellValue;
635
+ }
636
+ if (typeof value === 'number') {
637
+ return Number.isFinite(value) ? { tag: ValueTag.Number, value } : { tag: ValueTag.Error, code: ErrorCode.Num };
638
+ }
639
+ if (typeof value === 'boolean') {
640
+ return { tag: ValueTag.Boolean, value };
641
+ }
642
+ return stringCellValue(value);
643
+ }
644
+ function readTextRuns(xml) {
645
+ const runs = [...xml.matchAll(/<(?:[A-Za-z_][\w.-]*:)?t\b(?:[^>"']|"[^"]*"|'[^']*')*>([\s\S]*?)<\/(?:[A-Za-z_][\w.-]*:)?t>/gu)].map((match) => decodeXmlText(match[1] ?? ''));
646
+ return runs.length > 0 ? runs.join('') : '';
647
+ }
648
+ function readElementText(xml, name) {
649
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
650
+ return (new RegExp(`<(?:[A-Za-z_][\\w.-]*:)?${escapedName}\\b(?:[^>"']|"[^"]*"|'[^']*')*>([\\s\\S]*?)<\\/(?:[A-Za-z_][\\w.-]*:)?${escapedName}>`, 'u').exec(xml)?.[1] ?? null);
651
+ }
652
+ //# sourceMappingURL=streaming-native-recalc.js.map