@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.
- package/README.md +25 -0
- package/dist/address.d.ts +15 -0
- package/dist/address.js +82 -0
- package/dist/address.js.map +1 -0
- package/dist/external-workbook-types.d.ts +44 -0
- package/dist/external-workbook-types.js +12 -0
- package/dist/external-workbook-types.js.map +1 -0
- package/dist/file-source.d.ts +10 -0
- package/dist/file-source.js +76 -0
- package/dist/file-source.js.map +1 -0
- package/dist/formula-cache-reader.d.ts +30 -0
- package/dist/formula-cache-reader.js +350 -0
- package/dist/formula-cache-reader.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/simple-workbook-writer.d.ts +114 -0
- package/dist/simple-workbook-writer.js +621 -0
- package/dist/simple-workbook-writer.js.map +1 -0
- package/dist/source-preserving-literal-patches.d.ts +43 -0
- package/dist/source-preserving-literal-patches.js +615 -0
- package/dist/source-preserving-literal-patches.js.map +1 -0
- package/dist/source-preserving-zip.d.ts +25 -0
- package/dist/source-preserving-zip.js +341 -0
- package/dist/source-preserving-zip.js.map +1 -0
- package/dist/streaming-native-cell-arena.d.ts +57 -0
- package/dist/streaming-native-cell-arena.js +233 -0
- package/dist/streaming-native-cell-arena.js.map +1 -0
- package/dist/streaming-native-external-cache.d.ts +13 -0
- package/dist/streaming-native-external-cache.js +753 -0
- package/dist/streaming-native-external-cache.js.map +1 -0
- package/dist/streaming-native-inspect.d.ts +42 -0
- package/dist/streaming-native-inspect.js +297 -0
- package/dist/streaming-native-inspect.js.map +1 -0
- package/dist/streaming-native-lookup-wasm.d.ts +13 -0
- package/dist/streaming-native-lookup-wasm.js +250 -0
- package/dist/streaming-native-lookup-wasm.js.map +1 -0
- package/dist/streaming-native-recalc-evaluator.d.ts +17 -0
- package/dist/streaming-native-recalc-evaluator.js +743 -0
- package/dist/streaming-native-recalc-evaluator.js.map +1 -0
- package/dist/streaming-native-recalc.d.ts +97 -0
- package/dist/streaming-native-recalc.js +652 -0
- package/dist/streaming-native-recalc.js.map +1 -0
- package/dist/streaming-native-row-chain-conditionals.d.ts +8 -0
- package/dist/streaming-native-row-chain-conditionals.js +385 -0
- package/dist/streaming-native-row-chain-conditionals.js.map +1 -0
- package/dist/streaming-native-row-chain-dependencies.d.ts +17 -0
- package/dist/streaming-native-row-chain-dependencies.js +365 -0
- package/dist/streaming-native-row-chain-dependencies.js.map +1 -0
- package/dist/streaming-native-row-chain-references.d.ts +3 -0
- package/dist/streaming-native-row-chain-references.js +36 -0
- package/dist/streaming-native-row-chain-references.js.map +1 -0
- package/dist/streaming-native-row-chain-wasm.d.ts +56 -0
- package/dist/streaming-native-row-chain-wasm.js +546 -0
- package/dist/streaming-native-row-chain-wasm.js.map +1 -0
- package/dist/streaming-native-text.d.ts +2 -0
- package/dist/streaming-native-text.js +14 -0
- package/dist/streaming-native-text.js.map +1 -0
- package/dist/streaming-native-workbook-core.d.ts +47 -0
- package/dist/streaming-native-workbook-core.js +110 -0
- package/dist/streaming-native-workbook-core.js.map +1 -0
- package/dist/targeted-cell-reader.d.ts +11 -0
- package/dist/targeted-cell-reader.js +92 -0
- package/dist/targeted-cell-reader.js.map +1 -0
- package/dist/workbook-cell-reader.d.ts +29 -0
- package/dist/workbook-cell-reader.js +200 -0
- package/dist/workbook-cell-reader.js.map +1 -0
- package/dist/workbook-compatibility-report.d.ts +101 -0
- package/dist/workbook-compatibility-report.js +654 -0
- package/dist/workbook-compatibility-report.js.map +1 -0
- package/dist/workbook-sheet-paths.d.ts +8 -0
- package/dist/workbook-sheet-paths.js +79 -0
- package/dist/workbook-sheet-paths.js.map +1 -0
- package/dist/xml-part-patch.d.ts +12 -0
- package/dist/xml-part-patch.js +45 -0
- package/dist/xml-part-patch.js.map +1 -0
- package/dist/xml.d.ts +9 -0
- package/dist/xml.js +42 -0
- package/dist/xml.js.map +1 -0
- package/dist/zip-reader.d.ts +51 -0
- package/dist/zip-reader.js +448 -0
- package/dist/zip-reader.js.map +1 -0
- package/package.json +113 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @bilig/xlsx
|
|
2
|
+
|
|
3
|
+
`@bilig/xlsx` provides ZIP, address, and source-preserving patch primitives used
|
|
4
|
+
by Bilig runtime packages. This package does not depend on SheetJS or the
|
|
5
|
+
`xlsx` CDN tarball.
|
|
6
|
+
|
|
7
|
+
## Source-Preserving Literal Patches
|
|
8
|
+
|
|
9
|
+
Use `exportXlsxSourceLiteralPatchesToFileAsync` when a service needs to apply
|
|
10
|
+
scalar cell edits to an existing XLSX package while preserving untouched package
|
|
11
|
+
parts and avoiding full-source reads:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { exportXlsxSourceLiteralPatchesToFileAsync } from '@bilig/xlsx'
|
|
15
|
+
|
|
16
|
+
await exportXlsxSourceLiteralPatchesToFileAsync({
|
|
17
|
+
source: fileBackedXlsxReader,
|
|
18
|
+
outputPath: './patched.xlsx',
|
|
19
|
+
sheetNames: ['Inputs'],
|
|
20
|
+
patches: [{ sheetName: 'Inputs', address: 'B3', value: 96000 }],
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The file-backed source only needs `byteLength` and `readRange(start, end)` for
|
|
25
|
+
the streaming patch path. `readBytes()` is a fallback, not the normal path.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface XlsxCellAddress {
|
|
2
|
+
readonly r: number;
|
|
3
|
+
readonly c: number;
|
|
4
|
+
}
|
|
5
|
+
export interface XlsxCellRange {
|
|
6
|
+
readonly s: XlsxCellAddress;
|
|
7
|
+
readonly e: XlsxCellAddress;
|
|
8
|
+
}
|
|
9
|
+
export declare function decodeCellAddress(address: string): XlsxCellAddress;
|
|
10
|
+
export declare function encodeColumnAddress(columnIndex: number): string;
|
|
11
|
+
export declare function decodeColumnAddress(columnAddress: string): number;
|
|
12
|
+
export declare function encodeCellAddress(address: XlsxCellAddress): string;
|
|
13
|
+
export declare function normalizeCellAddress(address: string): string;
|
|
14
|
+
export declare function decodeCellRange(ref: string): XlsxCellRange;
|
|
15
|
+
export declare function encodeCellRange(range: XlsxCellRange): string;
|
package/dist/address.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const cellAddressPattern = /^\$?([A-Za-z]{1,4})\$?([1-9][0-9]*)$/u;
|
|
2
|
+
export function decodeCellAddress(address) {
|
|
3
|
+
const match = cellAddressPattern.exec(address);
|
|
4
|
+
if (!match) {
|
|
5
|
+
throw new Error(`Invalid XLSX cell address: ${address}`);
|
|
6
|
+
}
|
|
7
|
+
const rowNumber = Number(match[2]);
|
|
8
|
+
if (!Number.isSafeInteger(rowNumber) || rowNumber < 1) {
|
|
9
|
+
throw new Error(`Invalid XLSX row number: ${address}`);
|
|
10
|
+
}
|
|
11
|
+
let column = 0;
|
|
12
|
+
for (const character of match[1].toUpperCase()) {
|
|
13
|
+
const code = character.charCodeAt(0);
|
|
14
|
+
if (code < 65 || code > 90) {
|
|
15
|
+
throw new Error(`Invalid XLSX column address: ${address}`);
|
|
16
|
+
}
|
|
17
|
+
column = column * 26 + code - 64;
|
|
18
|
+
}
|
|
19
|
+
return { r: rowNumber - 1, c: column - 1 };
|
|
20
|
+
}
|
|
21
|
+
export function encodeColumnAddress(columnIndex) {
|
|
22
|
+
if (!Number.isSafeInteger(columnIndex) || columnIndex < 0) {
|
|
23
|
+
throw new Error(`Invalid XLSX column index: ${String(columnIndex)}`);
|
|
24
|
+
}
|
|
25
|
+
let column = columnIndex + 1;
|
|
26
|
+
let output = '';
|
|
27
|
+
while (column > 0) {
|
|
28
|
+
const remainder = (column - 1) % 26;
|
|
29
|
+
output = String.fromCharCode(65 + remainder) + output;
|
|
30
|
+
column = Math.floor((column - 1) / 26);
|
|
31
|
+
}
|
|
32
|
+
return output;
|
|
33
|
+
}
|
|
34
|
+
export function decodeColumnAddress(columnAddress) {
|
|
35
|
+
const normalized = columnAddress.trim().replaceAll('$', '').toUpperCase();
|
|
36
|
+
if (!/^[A-Z]{1,4}$/u.test(normalized)) {
|
|
37
|
+
throw new Error(`Invalid XLSX column address: ${columnAddress}`);
|
|
38
|
+
}
|
|
39
|
+
let column = 0;
|
|
40
|
+
for (const character of normalized) {
|
|
41
|
+
const code = character.charCodeAt(0);
|
|
42
|
+
if (code < 65 || code > 90) {
|
|
43
|
+
throw new Error(`Invalid XLSX column address: ${columnAddress}`);
|
|
44
|
+
}
|
|
45
|
+
column = column * 26 + code - 64;
|
|
46
|
+
}
|
|
47
|
+
return column - 1;
|
|
48
|
+
}
|
|
49
|
+
export function encodeCellAddress(address) {
|
|
50
|
+
if (!Number.isSafeInteger(address.r) || !Number.isSafeInteger(address.c) || address.r < 0 || address.c < 0) {
|
|
51
|
+
throw new Error(`Invalid XLSX cell coordinates: ${JSON.stringify(address)}`);
|
|
52
|
+
}
|
|
53
|
+
return `${encodeColumnAddress(address.c)}${String(address.r + 1)}`;
|
|
54
|
+
}
|
|
55
|
+
export function normalizeCellAddress(address) {
|
|
56
|
+
return encodeCellAddress(decodeCellAddress(address.replaceAll('$', '')));
|
|
57
|
+
}
|
|
58
|
+
export function decodeCellRange(ref) {
|
|
59
|
+
const parts = ref.split(':');
|
|
60
|
+
const [startRef, endRef] = parts;
|
|
61
|
+
if (!startRef || parts.length > 2) {
|
|
62
|
+
throw new Error(`Invalid XLSX cell range: ${ref}`);
|
|
63
|
+
}
|
|
64
|
+
const start = decodeCellAddress(startRef);
|
|
65
|
+
const end = decodeCellAddress(endRef ?? startRef);
|
|
66
|
+
return {
|
|
67
|
+
s: {
|
|
68
|
+
r: Math.min(start.r, end.r),
|
|
69
|
+
c: Math.min(start.c, end.c),
|
|
70
|
+
},
|
|
71
|
+
e: {
|
|
72
|
+
r: Math.max(start.r, end.r),
|
|
73
|
+
c: Math.max(start.c, end.c),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function encodeCellRange(range) {
|
|
78
|
+
const start = encodeCellAddress(range.s);
|
|
79
|
+
const end = encodeCellAddress(range.e);
|
|
80
|
+
return start === end ? start : `${start}:${end}`;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.js","sourceRoot":"","sources":["../src/address.ts"],"names":[],"mappings":"AAUA,MAAM,kBAAkB,GAAG,uCAAuC,CAAA;AAElE,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAA;IAC1D,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAClC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAA;IACxD,CAAC;IACD,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QACpC,IAAI,IAAI,GAAG,EAAE,IAAI,IAAI,GAAG,EAAE,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,gCAAgC,OAAO,EAAE,CAAC,CAAA;QAC5D,CAAC;QACD,MAAM,GAAG,MAAM,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,CAAA;IAClC,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,SAAS,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,8BAA8B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;IACtE,CAAC;IACD,IAAI,MAAM,GAAG,WAAW,GAAG,CAAC,CAAA;IAC5B,IAAI,MAAM,GAAG,EAAE,CAAA;IACf,OAAO,MAAM,GAAG,CAAC,EAAE,CAAC;QAClB,MAAM,SAAS,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,CAAA;QACnC,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,SAAS,CAAC,GAAG,MAAM,CAAA;QACrD,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAA;IACxC,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,aAAqB;IACvD,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IACzE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,aAAa,EAAE,CAAC,CAAA;IAClE,CAAC;IACD,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QACpC,IAAI,IAAI,GAAG,EAAE,IAAI,IAAI,GAAG,EAAE,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,gCAAgC,aAAa,EAAE,CAAC,CAAA;QAClE,CAAC;QACD,MAAM,GAAG,MAAM,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,CAAA;IAClC,CAAC;IACD,OAAO,MAAM,GAAG,CAAC,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAwB;IACxD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3G,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC9E,CAAC;IACD,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,OAAO,iBAAiB,CAAC,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,KAAK,CAAA;IAChC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,MAAM,KAAK,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IACzC,MAAM,GAAG,GAAG,iBAAiB,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAA;IACjD,OAAO;QACL,CAAC,EAAE;YACD,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAC3B,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;SAC5B;QACD,CAAC,EAAE;YACD,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAC3B,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;SAC5B;KACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAoB;IAClD,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACxC,MAAM,GAAG,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACtC,OAAO,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,GAAG,EAAE,CAAA;AAClD,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface XlsxExternalWorkbookInput {
|
|
2
|
+
readonly bytes: Uint8Array | ArrayBuffer;
|
|
3
|
+
readonly fileName?: string;
|
|
4
|
+
readonly workbookName?: string;
|
|
5
|
+
readonly target?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const xlsxExternalWorkbookByteInputLimit = 1000000;
|
|
8
|
+
export declare function assertXlsxExternalWorkbookByteInputWithinLimit(byteLength: number, fileName: string): void;
|
|
9
|
+
export type XlsxExternalLinkCacheArtifactMode = 'preserve-existing' | 'replace-refreshed';
|
|
10
|
+
export type XlsxExternalWorkbookHydrationStatus = 'refreshed' | 'skipped-no-match' | 'skipped-ambiguous-match' | 'skipped-empty-refresh';
|
|
11
|
+
export type XlsxExternalWorkbookHydrationMatchKind = 'exact-target' | 'unique-workbook-identity';
|
|
12
|
+
export interface XlsxExternalWorkbookHydrationReferenceDiagnostic {
|
|
13
|
+
readonly bookIndex: number;
|
|
14
|
+
readonly workbookName?: string;
|
|
15
|
+
readonly target?: string;
|
|
16
|
+
readonly status: XlsxExternalWorkbookHydrationStatus;
|
|
17
|
+
readonly candidateCount: number;
|
|
18
|
+
readonly referenceCandidateCount?: number;
|
|
19
|
+
readonly matchKind?: XlsxExternalWorkbookHydrationMatchKind;
|
|
20
|
+
readonly matchedFileName?: string;
|
|
21
|
+
readonly matchedWorkbookName?: string;
|
|
22
|
+
readonly matchedTarget?: string;
|
|
23
|
+
readonly refreshedSheetCount?: number;
|
|
24
|
+
readonly refreshedCellCount?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface XlsxExternalWorkbookHydrationDiagnostics {
|
|
27
|
+
readonly externalWorkbookCount: number;
|
|
28
|
+
readonly externalReferenceCount: number;
|
|
29
|
+
readonly refreshedBookIndices: readonly number[];
|
|
30
|
+
readonly refreshedSheetCount: number;
|
|
31
|
+
readonly refreshedCellCount: number;
|
|
32
|
+
readonly skippedNoMatchCount: number;
|
|
33
|
+
readonly skippedAmbiguousMatchCount: number;
|
|
34
|
+
readonly skippedEmptyRefreshCount: number;
|
|
35
|
+
readonly references: readonly XlsxExternalWorkbookHydrationReferenceDiagnostic[];
|
|
36
|
+
}
|
|
37
|
+
export interface ImportedWorkbookDiagnostics {
|
|
38
|
+
readonly externalWorkbookHydration?: XlsxExternalWorkbookHydrationDiagnostics;
|
|
39
|
+
}
|
|
40
|
+
export interface XlsxImportOptions {
|
|
41
|
+
readonly externalWorkbooks?: readonly XlsxExternalWorkbookInput[];
|
|
42
|
+
readonly externalLinkCacheArtifactMode?: XlsxExternalLinkCacheArtifactMode;
|
|
43
|
+
readonly preferNativeSimpleImport?: boolean;
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const xlsxExternalWorkbookByteInputLimit = 1_000_000;
|
|
2
|
+
export function assertXlsxExternalWorkbookByteInputWithinLimit(byteLength, fileName) {
|
|
3
|
+
if (byteLength <= xlsxExternalWorkbookByteInputLimit) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
throw new Error([
|
|
7
|
+
`external workbook byte input is small-workbook only: ${fileName} is ${byteLength} bytes`,
|
|
8
|
+
`limit is ${xlsxExternalWorkbookByteInputLimit} bytes`,
|
|
9
|
+
'Large external workbook hydration must use a native file-backed path before it can be enabled for production large-XLSX jobs.',
|
|
10
|
+
].join('; '));
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=external-workbook-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-workbook-types.js","sourceRoot":"","sources":["../src/external-workbook-types.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,kCAAkC,GAAG,SAAS,CAAA;AAE3D,MAAM,UAAU,8CAA8C,CAAC,UAAkB,EAAE,QAAgB;IACjG,IAAI,UAAU,IAAI,kCAAkC,EAAE,CAAC;QACrD,OAAM;IACR,CAAC;IACD,MAAM,IAAI,KAAK,CACb;QACE,wDAAwD,QAAQ,OAAO,UAAU,QAAQ;QACzF,YAAY,kCAAkC,QAAQ;QACtD,+HAA+H;KAChI,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { XlsxSourceReader } from './source-preserving-literal-patches.js';
|
|
2
|
+
import type { XlsxZipByteSource } from './zip-reader.js';
|
|
3
|
+
export type FileXlsxSourceReader = XlsxSourceReader & XlsxZipByteSource & {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
};
|
|
6
|
+
export interface FileXlsxSourceReaderOptions {
|
|
7
|
+
readonly maxReadBytes?: number | false;
|
|
8
|
+
}
|
|
9
|
+
export declare const defaultFileXlsxSourceReadBytesLimit = 1000000;
|
|
10
|
+
export declare function createFileXlsxSourceReader(path: string, options?: FileXlsxSourceReaderOptions): FileXlsxSourceReader;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { closeSync, openSync, readFileSync, readSync, statSync } from 'node:fs';
|
|
2
|
+
export const defaultFileXlsxSourceReadBytesLimit = 1_000_000;
|
|
3
|
+
export function createFileXlsxSourceReader(path, options = {}) {
|
|
4
|
+
const fd = openSync(path, 'r');
|
|
5
|
+
const byteLength = statSync(path).size;
|
|
6
|
+
const maxReadBytes = options.maxReadBytes ?? defaultFileXlsxSourceReadBytesLimit;
|
|
7
|
+
let closed = false;
|
|
8
|
+
const assertOpen = () => {
|
|
9
|
+
if (closed) {
|
|
10
|
+
throw new Error('XLSX file source has been released');
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const assertRange = (start, end) => {
|
|
14
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > byteLength) {
|
|
15
|
+
throw new Error(`Invalid XLSX file byte range: ${String(start)}..${String(end)}`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const assertReadBytesWithinLimit = () => {
|
|
19
|
+
if (maxReadBytes === false || byteLength <= maxReadBytes) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
throw new Error([
|
|
23
|
+
`XLSX file source readBytes is small-workbook only: ${path} is ${String(byteLength)} bytes`,
|
|
24
|
+
`limit is ${String(maxReadBytes)} bytes`,
|
|
25
|
+
'Use readRange/readRangeInto or a file-backed native XLSX API for large workbooks.',
|
|
26
|
+
].join('; '));
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
byteLength,
|
|
30
|
+
path,
|
|
31
|
+
readBytes() {
|
|
32
|
+
assertOpen();
|
|
33
|
+
assertReadBytesWithinLimit();
|
|
34
|
+
return readFileSync(path);
|
|
35
|
+
},
|
|
36
|
+
readRange(start, end) {
|
|
37
|
+
assertOpen();
|
|
38
|
+
assertRange(start, end);
|
|
39
|
+
const length = end - start;
|
|
40
|
+
const buffer = new Uint8Array(length);
|
|
41
|
+
let offset = 0;
|
|
42
|
+
while (offset < length) {
|
|
43
|
+
const bytesRead = readSync(fd, buffer, offset, length - offset, start + offset);
|
|
44
|
+
if (bytesRead === 0) {
|
|
45
|
+
throw new Error('Unexpected EOF while reading XLSX file source');
|
|
46
|
+
}
|
|
47
|
+
offset += bytesRead;
|
|
48
|
+
}
|
|
49
|
+
return buffer;
|
|
50
|
+
},
|
|
51
|
+
readRangeInto(start, end, target) {
|
|
52
|
+
assertOpen();
|
|
53
|
+
assertRange(start, end);
|
|
54
|
+
const length = end - start;
|
|
55
|
+
if (target.byteLength < length) {
|
|
56
|
+
throw new Error('XLSX file source scratch buffer is too small');
|
|
57
|
+
}
|
|
58
|
+
let offset = 0;
|
|
59
|
+
while (offset < length) {
|
|
60
|
+
const bytesRead = readSync(fd, target, offset, length - offset, start + offset);
|
|
61
|
+
if (bytesRead === 0) {
|
|
62
|
+
throw new Error('Unexpected EOF while reading XLSX file source');
|
|
63
|
+
}
|
|
64
|
+
offset += bytesRead;
|
|
65
|
+
}
|
|
66
|
+
return target.subarray(0, length);
|
|
67
|
+
},
|
|
68
|
+
release() {
|
|
69
|
+
if (!closed) {
|
|
70
|
+
closeSync(fd);
|
|
71
|
+
closed = true;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=file-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-source.js","sourceRoot":"","sources":["../src/file-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type StreamingNativeWorkbookCore } from './streaming-native-workbook-core.js';
|
|
2
|
+
export type XlsxFormulaCacheInspectionLimit = number | 'all';
|
|
3
|
+
export type XlsxFormulaCacheLiteral = string | number | boolean | null;
|
|
4
|
+
export type XlsxFormulaCacheReadPhase = 'open-source' | 'zip-central-directory' | 'workbook-metadata' | 'formula-cache-scan';
|
|
5
|
+
export type XlsxFormulaCacheReadErrorReason = 'invalid-or-zip64-xlsx' | 'worksheet-stream-unavailable';
|
|
6
|
+
export interface XlsxFormulaCacheCell {
|
|
7
|
+
readonly target: string;
|
|
8
|
+
readonly formula: string;
|
|
9
|
+
readonly cachedValue?: XlsxFormulaCacheLiteral;
|
|
10
|
+
}
|
|
11
|
+
export interface XlsxFormulaCacheScanResult {
|
|
12
|
+
readonly inputBytes: number;
|
|
13
|
+
readonly sheetNames: readonly string[];
|
|
14
|
+
readonly formulaCellCount: number;
|
|
15
|
+
readonly cells: readonly XlsxFormulaCacheCell[];
|
|
16
|
+
}
|
|
17
|
+
export declare class XlsxFormulaCacheReadError extends Error {
|
|
18
|
+
readonly reason: XlsxFormulaCacheReadErrorReason;
|
|
19
|
+
readonly inputBytes: number;
|
|
20
|
+
constructor(message: string, reason: XlsxFormulaCacheReadErrorReason, inputBytes: number);
|
|
21
|
+
}
|
|
22
|
+
export declare const defaultXlsxFormulaCacheInspectionLimit: XlsxFormulaCacheInspectionLimit;
|
|
23
|
+
export declare function readXlsxFormulaCacheCellsFromFile(inputPath: string, options?: {
|
|
24
|
+
readonly inspectLimit?: XlsxFormulaCacheInspectionLimit;
|
|
25
|
+
readonly onPhase?: (phase: XlsxFormulaCacheReadPhase) => void;
|
|
26
|
+
}): XlsxFormulaCacheScanResult;
|
|
27
|
+
export declare function readXlsxFormulaCacheCellsFromWorkbookCore(core: StreamingNativeWorkbookCore, options?: {
|
|
28
|
+
readonly inspectLimit?: XlsxFormulaCacheInspectionLimit;
|
|
29
|
+
}): XlsxFormulaCacheScanResult;
|
|
30
|
+
export declare function normalizeXlsxFormulaCacheInspectionLimit(limit: XlsxFormulaCacheInspectionLimit): XlsxFormulaCacheInspectionLimit;
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { translateFormulaReferences } from '@bilig/formula';
|
|
2
|
+
import { decodeCellAddress, encodeCellAddress } from './address.js';
|
|
3
|
+
import { closeStreamingNativeWorkbookCore, openStreamingNativeWorkbookCore, StreamingNativeWorkbookOpenError, } from './streaming-native-workbook-core.js';
|
|
4
|
+
import { decodeXmlText, getXmlElementText, readXmlAttribute } from './xml.js';
|
|
5
|
+
import { forEachInflatedXlsxZipEntryChunk } from './zip-reader.js';
|
|
6
|
+
export class XlsxFormulaCacheReadError extends Error {
|
|
7
|
+
reason;
|
|
8
|
+
inputBytes;
|
|
9
|
+
constructor(message, reason, inputBytes) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'XlsxFormulaCacheReadError';
|
|
12
|
+
this.reason = reason;
|
|
13
|
+
this.inputBytes = inputBytes;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const textDecoder = new TextDecoder();
|
|
17
|
+
const formulaElementPattern = /<((?:[A-Za-z_][\w.-]*:)?f)\b(?:[^>"']|"[^"]*"|'[^']*')*(?:\/>|>[\s\S]*?<\/\1>)/u;
|
|
18
|
+
const worksheetCellStartTagPattern = /<(?:[A-Za-z_][\w.-]*:)?c\b/u;
|
|
19
|
+
const worksheetCellCarryLength = 128;
|
|
20
|
+
export const defaultXlsxFormulaCacheInspectionLimit = 2000;
|
|
21
|
+
export function readXlsxFormulaCacheCellsFromFile(inputPath, options = {}) {
|
|
22
|
+
let core;
|
|
23
|
+
try {
|
|
24
|
+
core = openStreamingNativeWorkbookCore(inputPath, {
|
|
25
|
+
onPhase: (phase) => options.onPhase?.(phase),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof StreamingNativeWorkbookOpenError) {
|
|
30
|
+
throw new XlsxFormulaCacheReadError(error.message, error.reason, error.inputBytes);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const scan = options.inspectLimit === undefined
|
|
36
|
+
? readXlsxFormulaCacheCellsFromWorkbookCore(core)
|
|
37
|
+
: readXlsxFormulaCacheCellsFromWorkbookCore(core, { inspectLimit: options.inspectLimit });
|
|
38
|
+
options.onPhase?.('formula-cache-scan');
|
|
39
|
+
return scan;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
closeStreamingNativeWorkbookCore(core);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function readXlsxFormulaCacheCellsFromWorkbookCore(core, options = {}) {
|
|
46
|
+
const scan = collectXlsxFormulaCacheCells(core.zip, core.sheetEntries, normalizeXlsxFormulaCacheInspectionLimit(options.inspectLimit ?? defaultXlsxFormulaCacheInspectionLimit), core.inputBytes);
|
|
47
|
+
return {
|
|
48
|
+
inputBytes: core.inputBytes,
|
|
49
|
+
sheetNames: core.sheetNames,
|
|
50
|
+
formulaCellCount: scan.formulaCellCount,
|
|
51
|
+
cells: scan.cells,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function normalizeXlsxFormulaCacheInspectionLimit(limit) {
|
|
55
|
+
if (limit === 'all') {
|
|
56
|
+
return limit;
|
|
57
|
+
}
|
|
58
|
+
if (Number.isInteger(limit) && limit > 0) {
|
|
59
|
+
return limit;
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`Expected inspectLimit to be "all" or a positive integer, received: ${String(limit)}`);
|
|
62
|
+
}
|
|
63
|
+
function collectXlsxFormulaCacheCells(zip, sheetEntries, inspectionLimit, inputBytes) {
|
|
64
|
+
const pendingCells = [];
|
|
65
|
+
const sharedStringIndexes = new Set();
|
|
66
|
+
let formulaCellCount = 0;
|
|
67
|
+
let collectedCellCount = 0;
|
|
68
|
+
const shouldCollect = () => inspectionLimit === 'all' || collectedCellCount < inspectionLimit;
|
|
69
|
+
const markCollected = () => {
|
|
70
|
+
collectedCellCount += 1;
|
|
71
|
+
};
|
|
72
|
+
for (const sheet of sheetEntries) {
|
|
73
|
+
const scan = collectXlsxFormulaCacheCellsForSheet(zip, sheet.name, sheet.path, shouldCollect, markCollected, sharedStringIndexes, inputBytes);
|
|
74
|
+
formulaCellCount += scan.formulaCellCount;
|
|
75
|
+
for (const cell of scan.cells) {
|
|
76
|
+
pendingCells.push(cell);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const sharedStrings = readTargetSharedStrings(zip, sharedStringIndexes);
|
|
80
|
+
return {
|
|
81
|
+
formulaCellCount,
|
|
82
|
+
cells: pendingCells.map((cell) => {
|
|
83
|
+
const output = {
|
|
84
|
+
target: cell.target,
|
|
85
|
+
formula: cell.formula,
|
|
86
|
+
};
|
|
87
|
+
if (cell.cachedValue !== undefined) {
|
|
88
|
+
output.cachedValue = literalValueForPendingCacheInspection(cell.cachedValue, sharedStrings);
|
|
89
|
+
}
|
|
90
|
+
return output;
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function collectXlsxFormulaCacheCellsForSheet(zip, sheetName, sheetPath, shouldCollect, markCollected, sharedStringIndexes, inputBytes) {
|
|
95
|
+
const sharedFormulaMasters = new Map();
|
|
96
|
+
const cells = [];
|
|
97
|
+
let formulaCellCount = 0;
|
|
98
|
+
let buffer = '';
|
|
99
|
+
const processBuffer = (final) => {
|
|
100
|
+
let cell = shiftWorksheetCellXml(buffer, final);
|
|
101
|
+
while (cell.status === 'cell') {
|
|
102
|
+
buffer = cell.buffer;
|
|
103
|
+
const { cellXml, openingTag } = cell;
|
|
104
|
+
const addressText = openingTag ? readXmlAttribute(openingTag, 'r') : null;
|
|
105
|
+
if (addressText) {
|
|
106
|
+
let decodedAddress;
|
|
107
|
+
try {
|
|
108
|
+
decodedAddress = decodeCellAddress(addressText);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
decodedAddress = null;
|
|
112
|
+
}
|
|
113
|
+
const formula = decodedAddress ? readFormulaInfo(cellXml) : null;
|
|
114
|
+
if (decodedAddress && formula) {
|
|
115
|
+
formulaCellCount += 1;
|
|
116
|
+
if (formula.sharedFormulaIndex && formula.formula) {
|
|
117
|
+
sharedFormulaMasters.set(formula.sharedFormulaIndex, {
|
|
118
|
+
formula: formula.formula,
|
|
119
|
+
row: decodedAddress.r,
|
|
120
|
+
col: decodedAddress.c,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (shouldCollect()) {
|
|
124
|
+
const formulaText = formulaSourceForCacheInspection(formula, sharedFormulaMasters, decodedAddress.r, decodedAddress.c);
|
|
125
|
+
const cachedValue = cachedValueForFormulaCacheInspection(cellXml, openingTag, sharedStringIndexes);
|
|
126
|
+
cells.push({
|
|
127
|
+
target: formatQualifiedTarget(sheetName, encodeCellAddress(decodedAddress)),
|
|
128
|
+
formula: formulaText.startsWith('=') ? formulaText : `=${formulaText}`,
|
|
129
|
+
...(cachedValue === undefined ? {} : { cachedValue }),
|
|
130
|
+
});
|
|
131
|
+
markCollected();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
cell = shiftWorksheetCellXml(buffer, final);
|
|
136
|
+
}
|
|
137
|
+
buffer = cell.status === 'pending' ? cell.buffer : '';
|
|
138
|
+
};
|
|
139
|
+
const streamed = forEachInflatedXlsxZipEntryChunk(zip, sheetPath, (chunk) => {
|
|
140
|
+
buffer += textDecoder.decode(chunk, { stream: true });
|
|
141
|
+
processBuffer(false);
|
|
142
|
+
}, { chunkSize: 64 * 1024, forceStreamingInflate: true });
|
|
143
|
+
if (!streamed) {
|
|
144
|
+
throw new XlsxFormulaCacheReadError(`Unable to stream worksheet XML for ${sheetName}`, 'worksheet-stream-unavailable', inputBytes);
|
|
145
|
+
}
|
|
146
|
+
buffer += textDecoder.decode();
|
|
147
|
+
processBuffer(true);
|
|
148
|
+
return { formulaCellCount, cells };
|
|
149
|
+
}
|
|
150
|
+
function shiftWorksheetCellXml(buffer, final) {
|
|
151
|
+
const startMatch = worksheetCellStartTagPattern.exec(buffer);
|
|
152
|
+
if (!startMatch) {
|
|
153
|
+
return {
|
|
154
|
+
status: final ? 'done' : 'pending',
|
|
155
|
+
buffer: final ? '' : buffer.slice(Math.max(0, buffer.length - worksheetCellCarryLength)),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const startIndex = startMatch.index;
|
|
159
|
+
const openingEnd = findXmlTagEnd(buffer, startIndex);
|
|
160
|
+
if (openingEnd < 0) {
|
|
161
|
+
return { status: 'pending', buffer: buffer.slice(startIndex) };
|
|
162
|
+
}
|
|
163
|
+
const openingTag = buffer.slice(startIndex, openingEnd + 1);
|
|
164
|
+
const endIndex = openingTag.endsWith('/>') ? openingEnd + 1 : findXmlElementEnd(buffer, openingEnd + 1, openingTag);
|
|
165
|
+
if (endIndex < 0) {
|
|
166
|
+
return { status: 'pending', buffer: buffer.slice(startIndex) };
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
status: 'cell',
|
|
170
|
+
cellXml: buffer.slice(startIndex, endIndex),
|
|
171
|
+
openingTag,
|
|
172
|
+
buffer: buffer.slice(endIndex),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function findXmlTagEnd(xml, startIndex) {
|
|
176
|
+
let quote = null;
|
|
177
|
+
for (let index = startIndex; index < xml.length; index += 1) {
|
|
178
|
+
const char = xml[index];
|
|
179
|
+
if (quote) {
|
|
180
|
+
if (char === quote) {
|
|
181
|
+
quote = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (char === '"' || char === "'") {
|
|
185
|
+
quote = char;
|
|
186
|
+
}
|
|
187
|
+
else if (char === '>') {
|
|
188
|
+
return index;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return -1;
|
|
192
|
+
}
|
|
193
|
+
function findXmlElementEnd(xml, startIndex, openingTag) {
|
|
194
|
+
const tagName = /^<((?:[A-Za-z_][\w.-]*:)?c)\b/u.exec(openingTag)?.[1];
|
|
195
|
+
if (!tagName) {
|
|
196
|
+
return -1;
|
|
197
|
+
}
|
|
198
|
+
const closingPattern = new RegExp(`</${escapeRegExp(tagName)}\\s*>`, 'u');
|
|
199
|
+
const relativeMatch = closingPattern.exec(xml.slice(startIndex));
|
|
200
|
+
return relativeMatch ? startIndex + relativeMatch.index + relativeMatch[0].length : -1;
|
|
201
|
+
}
|
|
202
|
+
function readFormulaInfo(cellXml) {
|
|
203
|
+
const formulaXml = formulaElementPattern.exec(cellXml)?.[0];
|
|
204
|
+
if (!formulaXml) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const openingEnd = formulaXml.indexOf('>');
|
|
208
|
+
const openingTag = openingEnd >= 0 ? formulaXml.slice(0, openingEnd + 1) : formulaXml;
|
|
209
|
+
const sharedFormulaIndex = readXmlAttribute(openingTag, 'si');
|
|
210
|
+
const formula = formulaXml.endsWith('/>') || openingEnd < 0
|
|
211
|
+
? null
|
|
212
|
+
: decodeXmlText(formulaXml.slice(openingEnd + 1, formulaXml.replace(/<\/(?:[A-Za-z_][\w.-]*:)?f>\s*$/u, '').length));
|
|
213
|
+
return {
|
|
214
|
+
formula: formula && formula.trim().length > 0 ? formula : null,
|
|
215
|
+
sharedFormulaIndex,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function formulaSourceForCacheInspection(formula, sharedFormulaMasters, row, col) {
|
|
219
|
+
if (formula.formula) {
|
|
220
|
+
return formula.formula;
|
|
221
|
+
}
|
|
222
|
+
if (!formula.sharedFormulaIndex) {
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
const master = sharedFormulaMasters.get(formula.sharedFormulaIndex);
|
|
226
|
+
return master ? translateFormulaReferences(master.formula, row - master.row, col - master.col) : '';
|
|
227
|
+
}
|
|
228
|
+
function cachedValueForFormulaCacheInspection(cellXml, openingTag, sharedStringIndexes) {
|
|
229
|
+
if (getXmlElementText(cellXml, 'v') === null && readXmlAttribute(openingTag, 't') !== 'inlineStr') {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const value = readCellValue(cellXml, openingTag);
|
|
233
|
+
if (isSharedStringReference(value)) {
|
|
234
|
+
sharedStringIndexes.add(value.index);
|
|
235
|
+
}
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
function readCellValue(cellXml, openingTag) {
|
|
239
|
+
const type = readXmlAttribute(openingTag, 't');
|
|
240
|
+
if (type === 'inlineStr') {
|
|
241
|
+
return { kind: 'string', value: readTextRuns(cellXml) };
|
|
242
|
+
}
|
|
243
|
+
const rawValue = getXmlElementText(cellXml, 'v');
|
|
244
|
+
if (rawValue === null) {
|
|
245
|
+
return { kind: 'empty' };
|
|
246
|
+
}
|
|
247
|
+
if (type === 's') {
|
|
248
|
+
const index = Number(rawValue);
|
|
249
|
+
return Number.isSafeInteger(index) && index >= 0 ? { kind: 'shared-string', index } : { kind: 'empty' };
|
|
250
|
+
}
|
|
251
|
+
if (type === 'str') {
|
|
252
|
+
return { kind: 'string', value: decodeXmlText(rawValue) };
|
|
253
|
+
}
|
|
254
|
+
if (type === 'b') {
|
|
255
|
+
return { kind: 'boolean', value: rawValue === '1' || rawValue.toLowerCase() === 'true' };
|
|
256
|
+
}
|
|
257
|
+
if (type === 'e') {
|
|
258
|
+
return { kind: 'error', value: normalizeErrorText(decodeXmlText(rawValue)) };
|
|
259
|
+
}
|
|
260
|
+
const numeric = Number(rawValue);
|
|
261
|
+
return Number.isFinite(numeric) ? { kind: 'number', value: numeric } : { kind: 'string', value: decodeXmlText(rawValue) };
|
|
262
|
+
}
|
|
263
|
+
function literalValueForPendingCacheInspection(value, sharedStrings) {
|
|
264
|
+
switch (value.kind) {
|
|
265
|
+
case 'shared-string':
|
|
266
|
+
return sharedStrings.get(value.index) ?? '';
|
|
267
|
+
case 'empty':
|
|
268
|
+
return null;
|
|
269
|
+
case 'number':
|
|
270
|
+
return value.value;
|
|
271
|
+
case 'boolean':
|
|
272
|
+
return value.value;
|
|
273
|
+
case 'string':
|
|
274
|
+
return value.value;
|
|
275
|
+
case 'error':
|
|
276
|
+
return value.value;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function readTargetSharedStrings(zip, targetIndexes) {
|
|
280
|
+
const values = new Map();
|
|
281
|
+
if (targetIndexes.size === 0) {
|
|
282
|
+
return values;
|
|
283
|
+
}
|
|
284
|
+
let buffer = '';
|
|
285
|
+
let index = 0;
|
|
286
|
+
const processBuffer = (final) => {
|
|
287
|
+
const safeEnd = final ? buffer.length : Math.max(0, buffer.lastIndexOf('<si'));
|
|
288
|
+
if (safeEnd === 0 && !final) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
const safeXml = buffer.slice(0, safeEnd);
|
|
292
|
+
for (const match of safeXml.matchAll(/<((?:[A-Za-z_][\w.-]*:)?si)\b(?:[^>"']|"[^"]*"|'[^']*')*>[\s\S]*?<\/\1>/gu)) {
|
|
293
|
+
if (targetIndexes.has(index)) {
|
|
294
|
+
values.set(index, readTextRuns(match[0]));
|
|
295
|
+
}
|
|
296
|
+
index += 1;
|
|
297
|
+
}
|
|
298
|
+
buffer = buffer.slice(safeEnd);
|
|
299
|
+
return values.size < targetIndexes.size;
|
|
300
|
+
};
|
|
301
|
+
const streamed = forEachInflatedXlsxZipEntryChunk(zip, 'xl/sharedStrings.xml', (chunk) => {
|
|
302
|
+
buffer += textDecoder.decode(chunk, { stream: true });
|
|
303
|
+
return processBuffer(false);
|
|
304
|
+
}, { chunkSize: 64 * 1024, forceStreamingInflate: true });
|
|
305
|
+
if (!streamed) {
|
|
306
|
+
return values;
|
|
307
|
+
}
|
|
308
|
+
buffer += textDecoder.decode();
|
|
309
|
+
processBuffer(true);
|
|
310
|
+
return values;
|
|
311
|
+
}
|
|
312
|
+
function isSharedStringReference(value) {
|
|
313
|
+
return value.kind === 'shared-string';
|
|
314
|
+
}
|
|
315
|
+
function formatQualifiedTarget(sheetName, address) {
|
|
316
|
+
return `${quoteSheetName(sheetName)}!${address}`;
|
|
317
|
+
}
|
|
318
|
+
function quoteSheetName(sheetName) {
|
|
319
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/u.test(sheetName) ? sheetName : `'${sheetName.replaceAll("'", "''")}'`;
|
|
320
|
+
}
|
|
321
|
+
function normalizeErrorText(value) {
|
|
322
|
+
switch (value.toUpperCase()) {
|
|
323
|
+
case '#DIV/0!':
|
|
324
|
+
return '#DIV/0!';
|
|
325
|
+
case '#REF!':
|
|
326
|
+
return '#REF!';
|
|
327
|
+
case '#VALUE!':
|
|
328
|
+
return '#VALUE!';
|
|
329
|
+
case '#NAME?':
|
|
330
|
+
return '#NAME?';
|
|
331
|
+
case '#N/A':
|
|
332
|
+
return '#N/A';
|
|
333
|
+
case '#NUM!':
|
|
334
|
+
return '#NUM!';
|
|
335
|
+
case '#FIELD!':
|
|
336
|
+
return '#FIELD!';
|
|
337
|
+
case '#NULL!':
|
|
338
|
+
return '#NULL!';
|
|
339
|
+
default:
|
|
340
|
+
return '#VALUE!';
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function readTextRuns(xml) {
|
|
344
|
+
const runs = [...xml.matchAll(/<(?:[A-Za-z_][\w.-]*:)?t\b(?:[^>"']|"[^"]*"|'[^']*')*>([\s\S]*?)<\/(?:[A-Za-z_][\w.-]*:)?t>/gu)].map((match) => decodeXmlText(match[1] ?? ''));
|
|
345
|
+
return runs.length > 0 ? runs.join('') : '';
|
|
346
|
+
}
|
|
347
|
+
function escapeRegExp(value) {
|
|
348
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=formula-cache-reader.js.map
|