@bendyline/squisq-formats 1.4.1 → 2.0.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 +48 -2
- package/dist/{chunk-WWBIKRNP.js → chunk-2CSZJEAL.js} +13 -11
- package/dist/chunk-2CSZJEAL.js.map +1 -0
- package/dist/{chunk-RTS5XBZ7.js → chunk-2EFDWKC6.js} +262 -84
- package/dist/chunk-2EFDWKC6.js.map +1 -0
- package/dist/chunk-2PRVLAU3.js +740 -0
- package/dist/chunk-2PRVLAU3.js.map +1 -0
- package/dist/{chunk-SHLPSADL.js → chunk-4HJ6AK2Q.js} +54 -193
- package/dist/chunk-4HJ6AK2Q.js.map +1 -0
- package/dist/{chunk-5LSSWZPU.js → chunk-4VUWTSGM.js} +72 -25
- package/dist/chunk-4VUWTSGM.js.map +1 -0
- package/dist/{chunk-YESS7VY3.js → chunk-CVNVD7VH.js} +154 -39
- package/dist/chunk-CVNVD7VH.js.map +1 -0
- package/dist/chunk-CX3LH47J.js +99 -0
- package/dist/chunk-CX3LH47J.js.map +1 -0
- package/dist/{chunk-D7IFPWNZ.js → chunk-GVS2XXV6.js} +27 -26
- package/dist/chunk-GVS2XXV6.js.map +1 -0
- package/dist/chunk-H7T5DXUU.js +479 -0
- package/dist/chunk-H7T5DXUU.js.map +1 -0
- package/dist/{chunk-EHLGMSTE.js → chunk-HBSCRS5P.js} +10 -8
- package/dist/chunk-HBSCRS5P.js.map +1 -0
- package/dist/{chunk-46FMDOWZ.js → chunk-LXYLOOST.js} +49 -17
- package/dist/chunk-LXYLOOST.js.map +1 -0
- package/dist/{chunk-MJGRI6XR.js → chunk-MLX2BOJC.js} +12 -7
- package/dist/chunk-MLX2BOJC.js.map +1 -0
- package/dist/chunk-NKAJPJ4G.js +106 -0
- package/dist/chunk-NKAJPJ4G.js.map +1 -0
- package/dist/{chunk-DQAZR57U.js → chunk-O3GVVND4.js} +1 -1
- package/dist/chunk-ODL3SSPT.js +18 -0
- package/dist/chunk-ODL3SSPT.js.map +1 -0
- package/dist/chunk-X4USWOQ2.js +131 -0
- package/dist/chunk-X4USWOQ2.js.map +1 -0
- package/dist/chunk-XTLQXR4U.js +195 -0
- package/dist/chunk-XTLQXR4U.js.map +1 -0
- package/dist/chunk-YRT7GQ5Y.js +28 -0
- package/dist/chunk-YRT7GQ5Y.js.map +1 -0
- package/dist/chunk-ZZ7D4DN6.js +97 -0
- package/dist/chunk-ZZ7D4DN6.js.map +1 -0
- package/dist/container/index.d.ts +6 -2
- package/dist/container/index.js +15 -17
- package/dist/container/index.js.map +1 -1
- package/dist/docx/index.d.ts +6 -2
- package/dist/docx/index.js +5 -3
- package/dist/epub/index.d.ts +3 -1
- package/dist/epub/index.js +3 -3
- package/dist/export-Djev9BLU.d.ts +69 -0
- package/dist/extract-LNOA7DBK.js +15 -0
- package/dist/extract-LNOA7DBK.js.map +1 -0
- package/dist/html/index.d.ts +15 -85
- package/dist/html/index.js +3 -3
- package/dist/images-7FBWPKE3.js +7 -0
- package/dist/images-7FBWPKE3.js.map +1 -0
- package/dist/import-DTkDxHmZ.d.ts +84 -0
- package/dist/import-Yivc3kZM.d.ts +136 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +30 -11
- package/dist/infer/index.d.ts +138 -0
- package/dist/infer/index.js +28 -0
- package/dist/infer/index.js.map +1 -0
- package/dist/layouts-7OUUVAGU.js +12 -0
- package/dist/layouts-7OUUVAGU.js.map +1 -0
- package/dist/layouts-CZ0bdsNL.d.ts +150 -0
- package/dist/mapTheme-IR27S6IV.js +11 -0
- package/dist/mapTheme-IR27S6IV.js.map +1 -0
- package/dist/ooxml/index.d.ts +21 -127
- package/dist/ooxml/index.js +31 -9
- package/dist/pdf/index.d.ts +3 -1
- package/dist/pdf/index.js +1 -1
- package/dist/pptx/index.d.ts +8 -108
- package/dist/pptx/index.js +13 -4
- package/dist/reader-Gof6OrJX.d.ts +140 -0
- package/dist/registry/index.d.ts +46 -8
- package/dist/registry/index.js +5 -2
- package/dist/themeReader-DPw4J5Ss.d.ts +56 -0
- package/dist/xlsx/index.d.ts +6 -66
- package/dist/xlsx/index.js +4 -2
- package/dist/zipLimits-bGYMS9cI.d.ts +29 -0
- package/package.json +8 -2
- package/src/__tests__/container.test.ts +41 -0
- package/src/__tests__/convert.test.ts +167 -0
- package/src/__tests__/epub.test.ts +102 -1
- package/src/__tests__/html.test.ts +62 -0
- package/src/__tests__/inferTheme.test.ts +135 -0
- package/src/__tests__/lossyWarnings.test.ts +41 -0
- package/src/__tests__/ooxml.test.ts +59 -2
- package/src/__tests__/ooxmlThemeReader.test.ts +92 -0
- package/src/__tests__/plainHtml.test.ts +15 -1
- package/src/__tests__/plainHtmlBundle.test.ts +18 -0
- package/src/__tests__/pptxImport.test.ts +30 -0
- package/src/__tests__/pptxInferFixtures.ts +314 -0
- package/src/__tests__/pptxLayoutInfer.test.ts +395 -0
- package/src/__tests__/zipSafety.test.ts +317 -0
- package/src/container/index.ts +20 -29
- package/src/docx/export.ts +4 -2
- package/src/docx/import.ts +4 -3
- package/src/epub/export.ts +94 -25
- package/src/html/docsHtmlBundle.ts +6 -2
- package/src/html/htmlTemplate.ts +31 -6
- package/src/html/imageUtils.ts +14 -5
- package/src/html/index.ts +12 -1
- package/src/html/plainHtml.ts +7 -3
- package/src/html/plainHtmlBundle.ts +11 -9
- package/src/index.ts +20 -0
- package/src/infer/extract.ts +127 -0
- package/src/infer/index.ts +168 -0
- package/src/infer/mapTheme.ts +176 -0
- package/src/infer/types.ts +27 -0
- package/src/ooxml/index.ts +14 -0
- package/src/ooxml/readUtils.ts +44 -0
- package/src/ooxml/reader.ts +87 -28
- package/src/ooxml/themeReader.ts +197 -0
- package/src/ooxml/types.ts +8 -9
- package/src/ooxml/writer.ts +28 -10
- package/src/pdf/export.ts +4 -2
- package/src/pdf/import.ts +40 -34
- package/src/pptx/export.ts +4 -2
- package/src/pptx/import.ts +214 -32
- package/src/pptx/index.ts +14 -0
- package/src/pptx/layouts.ts +1168 -0
- package/src/registry/convert.ts +54 -23
- package/src/registry/defaultFormats.ts +131 -44
- package/src/registry/index.ts +3 -0
- package/src/registry/types.ts +52 -6
- package/src/shared/boundedZipArchive.ts +342 -0
- package/src/shared/fidelity.ts +130 -0
- package/src/shared/zipEntryCount.ts +97 -0
- package/src/shared/zipLimits.ts +286 -0
- package/src/shared/zipSafety.ts +19 -0
- package/src/xlsx/import.ts +3 -2
- package/dist/chunk-46FMDOWZ.js.map +0 -1
- package/dist/chunk-5LSSWZPU.js.map +0 -1
- package/dist/chunk-D7IFPWNZ.js.map +0 -1
- package/dist/chunk-EHLGMSTE.js.map +0 -1
- package/dist/chunk-MJGRI6XR.js.map +0 -1
- package/dist/chunk-RTS5XBZ7.js.map +0 -1
- package/dist/chunk-SHLPSADL.js.map +0 -1
- package/dist/chunk-WWBIKRNP.js.map +0 -1
- package/dist/chunk-YESS7VY3.js.map +0 -1
- /package/dist/{chunk-DQAZR57U.js.map → chunk-O3GVVND4.js.map} +0 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
// src/shared/zipLimits.ts
|
|
2
|
+
var ZipSafetyError = class extends Error {
|
|
3
|
+
constructor(code, message, options = {}) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "ZipSafetyError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.path = options.path;
|
|
8
|
+
this.limit = options.limit;
|
|
9
|
+
this.actual = options.actual;
|
|
10
|
+
if (options.cause !== void 0) {
|
|
11
|
+
this.cause = options.cause;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var DEFAULT_MAX_ZIP_ENTRIES = 1e4;
|
|
16
|
+
var DEFAULT_MAX_ZIP_UNCOMPRESSED_BYTES = 512 * 1024 * 1024;
|
|
17
|
+
var DEFAULT_MAX_ZIP_COMPRESSION_RATIO = 1e3;
|
|
18
|
+
function validateZipArchive(zip, limits = {}) {
|
|
19
|
+
const resolved = resolveZipSafetyLimits(limits);
|
|
20
|
+
const files = [];
|
|
21
|
+
let memberCount = 0;
|
|
22
|
+
let declaredTotal = 0;
|
|
23
|
+
let oversizedEntry;
|
|
24
|
+
zip.forEach((relativePath, entry) => {
|
|
25
|
+
memberCount++;
|
|
26
|
+
if (entry.dir) return;
|
|
27
|
+
const originalPath = originalEntryPath(entry, relativePath);
|
|
28
|
+
if (!isSafeArchivePath(originalPath)) {
|
|
29
|
+
throw new ZipSafetyError(
|
|
30
|
+
"unsafe-path",
|
|
31
|
+
`ZIP import: archive contains an unsafe path: ${originalPath}.`,
|
|
32
|
+
{ path: originalPath }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const metadata = compressedMetadata(entry, originalPath);
|
|
36
|
+
if (metadata.uncompressedSize !== void 0 && metadata.uncompressedSize > resolved.maxEntryUncompressedBytes) {
|
|
37
|
+
oversizedEntry ?? (oversizedEntry = {
|
|
38
|
+
path: originalPath,
|
|
39
|
+
actual: metadata.uncompressedSize,
|
|
40
|
+
limit: resolved.maxEntryUncompressedBytes
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const ratio = metadata.uncompressedSize === void 0 || metadata.compressedSize === void 0 ? void 0 : compressionRatio(metadata.uncompressedSize, metadata.compressedSize);
|
|
44
|
+
if (ratio !== void 0 && ratio > resolved.maxCompressionRatio) {
|
|
45
|
+
throw compressionRatioError(originalPath, ratio, resolved.maxCompressionRatio);
|
|
46
|
+
}
|
|
47
|
+
declaredTotal += metadata.uncompressedSize ?? 0;
|
|
48
|
+
if (!Number.isSafeInteger(declaredTotal)) throw invalidMetadataError(originalPath);
|
|
49
|
+
files.push({
|
|
50
|
+
path: relativePath,
|
|
51
|
+
entry,
|
|
52
|
+
...metadata.uncompressedSize !== void 0 ? { declaredSize: metadata.uncompressedSize } : {},
|
|
53
|
+
...metadata.compressedSize !== void 0 ? { compressedSize: metadata.compressedSize } : {},
|
|
54
|
+
...metadata.crc32 !== void 0 ? { crc32: metadata.crc32 } : {}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
if (memberCount > resolved.maxEntries) {
|
|
58
|
+
throw tooManyEntriesError(memberCount, resolved.maxEntries);
|
|
59
|
+
}
|
|
60
|
+
if (declaredTotal > resolved.maxUncompressedBytes) {
|
|
61
|
+
throw archiveTooLargeError(declaredTotal, resolved.maxUncompressedBytes);
|
|
62
|
+
}
|
|
63
|
+
if (oversizedEntry) {
|
|
64
|
+
throw entryTooLargeError(oversizedEntry.path, oversizedEntry.actual, oversizedEntry.limit);
|
|
65
|
+
}
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
function assertSafeZipPath(path) {
|
|
69
|
+
if (!isSafeArchivePath(path)) {
|
|
70
|
+
throw new ZipSafetyError("unsafe-path", `ZIP archive path is unsafe: ${path}.`, { path });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function resolveZipSafetyLimits(limits) {
|
|
74
|
+
const maxEntries = limits.maxEntries ?? DEFAULT_MAX_ZIP_ENTRIES;
|
|
75
|
+
const maxUncompressedBytes = limits.maxUncompressedBytes ?? DEFAULT_MAX_ZIP_UNCOMPRESSED_BYTES;
|
|
76
|
+
const maxEntryUncompressedBytes = limits.maxEntryUncompressedBytes ?? maxUncompressedBytes;
|
|
77
|
+
const maxCompressionRatio = limits.maxCompressionRatio ?? DEFAULT_MAX_ZIP_COMPRESSION_RATIO;
|
|
78
|
+
assertNonNegativeInteger("maxEntries", maxEntries);
|
|
79
|
+
assertNonNegativeFinite("maxUncompressedBytes", maxUncompressedBytes);
|
|
80
|
+
assertNonNegativeFinite("maxEntryUncompressedBytes", maxEntryUncompressedBytes);
|
|
81
|
+
if (!Number.isFinite(maxCompressionRatio) || maxCompressionRatio < 1) {
|
|
82
|
+
throw invalidLimitError(
|
|
83
|
+
"maxCompressionRatio must be a finite number greater than or equal to 1"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
maxEntries,
|
|
88
|
+
maxEntryUncompressedBytes,
|
|
89
|
+
maxUncompressedBytes,
|
|
90
|
+
maxCompressionRatio
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function assertNonNegativeFinite(name, value) {
|
|
94
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
95
|
+
throw invalidLimitError(`${name} must be a non-negative finite number`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function entryTooLargeError(path, actual, limit) {
|
|
99
|
+
return new ZipSafetyError(
|
|
100
|
+
"entry-too-large",
|
|
101
|
+
`ZIP import: archive member ${path} exceeds ${limit} byte per-entry limit.`,
|
|
102
|
+
{ path, limit, actual }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
function tooManyEntriesError(actual, limit) {
|
|
106
|
+
return new ZipSafetyError(
|
|
107
|
+
"too-many-entries",
|
|
108
|
+
`ZIP import: archive has ${actual} files; limit is ${limit}.`,
|
|
109
|
+
{ limit, actual }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function archiveTooLargeError(actual, limit) {
|
|
113
|
+
return new ZipSafetyError(
|
|
114
|
+
"archive-too-large",
|
|
115
|
+
`ZIP import: uncompressed content exceeds ${limit} byte limit.`,
|
|
116
|
+
{ limit, actual }
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function compressionRatioError(path, actual, limit) {
|
|
120
|
+
return new ZipSafetyError(
|
|
121
|
+
"compression-ratio-exceeded",
|
|
122
|
+
`ZIP import: archive member ${path} exceeds ${limit}:1 compression-ratio limit.`,
|
|
123
|
+
{ path, limit, actual }
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
function compressionRatio(uncompressedBytes, compressedBytes) {
|
|
127
|
+
if (uncompressedBytes === 0) return 0;
|
|
128
|
+
if (compressedBytes === 0) return Number.POSITIVE_INFINITY;
|
|
129
|
+
return uncompressedBytes / compressedBytes;
|
|
130
|
+
}
|
|
131
|
+
function assertNonNegativeInteger(name, value) {
|
|
132
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
133
|
+
throw invalidLimitError(`${name} must be a non-negative integer`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function invalidLimitError(detail) {
|
|
137
|
+
return new ZipSafetyError("invalid-limit", `ZIP import: ${detail}.`);
|
|
138
|
+
}
|
|
139
|
+
function originalEntryPath(entry, relativePath) {
|
|
140
|
+
return entry.unsafeOriginalName ?? relativePath;
|
|
141
|
+
}
|
|
142
|
+
function compressedMetadata(entry, path) {
|
|
143
|
+
const data = entry._data;
|
|
144
|
+
const uncompressedSize = data?.uncompressedSize;
|
|
145
|
+
const compressedSize = data?.compressedSize;
|
|
146
|
+
const crc32 = data?.crc32;
|
|
147
|
+
if (uncompressedSize !== void 0 && !isValidMetadataInteger(uncompressedSize) || compressedSize !== void 0 && !isValidMetadataInteger(compressedSize) || crc32 !== void 0 && !Number.isInteger(crc32)) {
|
|
148
|
+
throw invalidMetadataError(path);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
...uncompressedSize !== void 0 ? { uncompressedSize } : {},
|
|
152
|
+
...compressedSize !== void 0 ? { compressedSize } : {},
|
|
153
|
+
...crc32 !== void 0 ? { crc32 } : {}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function isValidMetadataInteger(value) {
|
|
157
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
158
|
+
}
|
|
159
|
+
function invalidMetadataError(path) {
|
|
160
|
+
return new ZipSafetyError(
|
|
161
|
+
"invalid-entry-metadata",
|
|
162
|
+
`ZIP import: archive member has invalid size or checksum metadata: ${path}.`,
|
|
163
|
+
{ path }
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
function isSafeArchivePath(path) {
|
|
167
|
+
const segments = path.split("/");
|
|
168
|
+
return !(path.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(path) || path.includes("\\") || Array.from(path).some((character) => {
|
|
169
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
170
|
+
return codePoint < 32 || codePoint === 127;
|
|
171
|
+
}) || segments.some((segment) => segment === "" || segment === "." || segment === ".."));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/shared/boundedZipArchive.ts
|
|
175
|
+
import JSZip from "jszip";
|
|
176
|
+
|
|
177
|
+
// src/shared/zipEntryCount.ts
|
|
178
|
+
var END_OF_CENTRAL_DIRECTORY = 101010256;
|
|
179
|
+
var ZIP64_END_OF_CENTRAL_DIRECTORY = 101075792;
|
|
180
|
+
var ZIP64_END_LOCATOR = 117853008;
|
|
181
|
+
var UINT16_MAX = 65535;
|
|
182
|
+
var MIN_EOCD_BYTES = 22;
|
|
183
|
+
var MAX_ZIP_COMMENT_BYTES = 65535;
|
|
184
|
+
async function prepareZipInput(data) {
|
|
185
|
+
if (ArrayBuffer.isView(data)) {
|
|
186
|
+
const bytes2 = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
187
|
+
return { input: bytes2, bytes: bytes2 };
|
|
188
|
+
}
|
|
189
|
+
if (!isBlobLike(data)) {
|
|
190
|
+
const bytes2 = new Uint8Array(data);
|
|
191
|
+
return { input: bytes2, bytes: bytes2 };
|
|
192
|
+
}
|
|
193
|
+
const bytes = await readBlobBytes(data);
|
|
194
|
+
return bytes ? { input: bytes, bytes } : { input: data };
|
|
195
|
+
}
|
|
196
|
+
function declaredZipEntryCount(bytes) {
|
|
197
|
+
const eocdOffset = findEocd(bytes);
|
|
198
|
+
if (eocdOffset === void 0) return void 0;
|
|
199
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
200
|
+
const count = view.getUint16(eocdOffset + 10, true);
|
|
201
|
+
if (count !== UINT16_MAX) return count;
|
|
202
|
+
return readZip64EntryCount(view, eocdOffset);
|
|
203
|
+
}
|
|
204
|
+
function findEocd(bytes) {
|
|
205
|
+
if (bytes.byteLength < MIN_EOCD_BYTES) return void 0;
|
|
206
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
207
|
+
const earliest = Math.max(0, bytes.byteLength - MIN_EOCD_BYTES - MAX_ZIP_COMMENT_BYTES);
|
|
208
|
+
for (let offset = bytes.byteLength - MIN_EOCD_BYTES; offset >= earliest; offset--) {
|
|
209
|
+
if (view.getUint32(offset, true) !== END_OF_CENTRAL_DIRECTORY) continue;
|
|
210
|
+
const commentLength = view.getUint16(offset + 20, true);
|
|
211
|
+
if (offset + MIN_EOCD_BYTES + commentLength === bytes.byteLength) return offset;
|
|
212
|
+
}
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
function readZip64EntryCount(view, eocdOffset) {
|
|
216
|
+
const locatorOffset = eocdOffset - 20;
|
|
217
|
+
if (locatorOffset < 0 || view.getUint32(locatorOffset, true) !== ZIP64_END_LOCATOR) {
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
const recordOffset = readSafeUint64(view, locatorOffset + 8);
|
|
221
|
+
if (recordOffset === void 0 || recordOffset < 0 || recordOffset + 40 > view.byteLength || view.getUint32(recordOffset, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY) {
|
|
222
|
+
return void 0;
|
|
223
|
+
}
|
|
224
|
+
return readSafeUint64(view, recordOffset + 32);
|
|
225
|
+
}
|
|
226
|
+
function readSafeUint64(view, offset) {
|
|
227
|
+
if (offset < 0 || offset + 8 > view.byteLength) return void 0;
|
|
228
|
+
const low = view.getUint32(offset, true);
|
|
229
|
+
const high = view.getUint32(offset + 4, true);
|
|
230
|
+
const value = high * 4294967296 + low;
|
|
231
|
+
return Number.isSafeInteger(value) ? value : void 0;
|
|
232
|
+
}
|
|
233
|
+
function isBlobLike(data) {
|
|
234
|
+
const candidate = data;
|
|
235
|
+
return typeof candidate.size === "number" && typeof candidate.slice === "function";
|
|
236
|
+
}
|
|
237
|
+
async function readBlobBytes(blob) {
|
|
238
|
+
const arrayBuffer = blob.arrayBuffer;
|
|
239
|
+
if (typeof arrayBuffer === "function") {
|
|
240
|
+
return new Uint8Array(await arrayBuffer.call(blob));
|
|
241
|
+
}
|
|
242
|
+
if (typeof FileReader === "undefined") return void 0;
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
const reader = new FileReader();
|
|
245
|
+
reader.onload = () => resolve(new Uint8Array(reader.result));
|
|
246
|
+
reader.onerror = () => reject(reader.error ?? new Error("Could not read ZIP blob."));
|
|
247
|
+
reader.readAsArrayBuffer(blob);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/shared/boundedZipArchive.ts
|
|
252
|
+
var JsZipBoundedArchive = class {
|
|
253
|
+
constructor(zip, entries, limits) {
|
|
254
|
+
this.reads = /* @__PURE__ */ new Map();
|
|
255
|
+
this.activeReads = /* @__PURE__ */ new Set();
|
|
256
|
+
this.emittedBytes = 0;
|
|
257
|
+
this.zip = zip;
|
|
258
|
+
this.entries = entries;
|
|
259
|
+
this.limits = limits;
|
|
260
|
+
this.entriesByPath = new Map(entries.map((entry) => [entry.path, entry]));
|
|
261
|
+
}
|
|
262
|
+
/** Actual uncompressed bytes accepted from JSZip streams so far. */
|
|
263
|
+
get emittedUncompressedBytes() {
|
|
264
|
+
return this.emittedBytes;
|
|
265
|
+
}
|
|
266
|
+
/** Read and cache one member, returning null when it does not exist. */
|
|
267
|
+
async read(path, maxBytes) {
|
|
268
|
+
this.throwIfFailed();
|
|
269
|
+
if (maxBytes !== void 0) assertNonNegativeFinite("member read limit", maxBytes);
|
|
270
|
+
const entry = this.entriesByPath.get(path);
|
|
271
|
+
if (!entry) return null;
|
|
272
|
+
let pending = this.reads.get(path);
|
|
273
|
+
if (!pending) {
|
|
274
|
+
pending = this.streamEntry(entry, maxBytes);
|
|
275
|
+
this.reads.set(path, pending);
|
|
276
|
+
}
|
|
277
|
+
const bytes = await pending;
|
|
278
|
+
if (maxBytes !== void 0 && bytes.byteLength > maxBytes) {
|
|
279
|
+
const error = entryTooLargeError(path, bytes.byteLength, maxBytes);
|
|
280
|
+
this.failArchive(error);
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
return bytes;
|
|
284
|
+
}
|
|
285
|
+
/** Read one UTF-8 text member, returning null when it does not exist. */
|
|
286
|
+
async readText(path, maxBytes) {
|
|
287
|
+
const bytes = await this.read(path, maxBytes);
|
|
288
|
+
return bytes ? new TextDecoder().decode(bytes) : null;
|
|
289
|
+
}
|
|
290
|
+
/** Release a cached member when a sequential consumer has taken ownership. */
|
|
291
|
+
release(path) {
|
|
292
|
+
this.reads.delete(path);
|
|
293
|
+
}
|
|
294
|
+
streamEntry(metadata, readLimit) {
|
|
295
|
+
const { path, entry, declaredSize, crc32: expectedCrc } = metadata;
|
|
296
|
+
const effectiveEntryLimit = Math.min(
|
|
297
|
+
this.limits.maxEntryUncompressedBytes,
|
|
298
|
+
readLimit ?? Number.POSITIVE_INFINITY
|
|
299
|
+
);
|
|
300
|
+
let output = new Uint8Array(0);
|
|
301
|
+
let stream;
|
|
302
|
+
try {
|
|
303
|
+
stream = entry.internalStream("uint8array");
|
|
304
|
+
} catch (error) {
|
|
305
|
+
const safeError = asZipSafetyError(error, path);
|
|
306
|
+
this.failArchive(safeError);
|
|
307
|
+
return Promise.reject(safeError);
|
|
308
|
+
}
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
let settled = false;
|
|
311
|
+
let written = 0;
|
|
312
|
+
let crc = 4294967295;
|
|
313
|
+
const controller = {
|
|
314
|
+
stop: (error) => {
|
|
315
|
+
if (settled) return;
|
|
316
|
+
settled = true;
|
|
317
|
+
this.activeReads.delete(controller);
|
|
318
|
+
try {
|
|
319
|
+
stream.pause();
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
322
|
+
reject(error);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
this.activeReads.add(controller);
|
|
326
|
+
stream.on("data", (chunk) => {
|
|
327
|
+
if (settled) return;
|
|
328
|
+
const nextWritten = written + chunk.byteLength;
|
|
329
|
+
try {
|
|
330
|
+
this.assertChunkAllowed(metadata, nextWritten, chunk.byteLength, effectiveEntryLimit);
|
|
331
|
+
output = growOutput(output, nextWritten, declaredSize, effectiveEntryLimit);
|
|
332
|
+
output.set(chunk, written);
|
|
333
|
+
written = nextWritten;
|
|
334
|
+
crc = updateCrc32(crc, chunk);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
this.failArchive(asZipSafetyError(error, path));
|
|
337
|
+
}
|
|
338
|
+
}).on("error", (error) => {
|
|
339
|
+
if (settled) return;
|
|
340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
+
const code = message.includes("uncompressed data size mismatch") ? "size-mismatch" : "decompression-failed";
|
|
342
|
+
this.failArchive(
|
|
343
|
+
new ZipSafetyError(code, `ZIP import: could not decompress ${path}: ${message}`, {
|
|
344
|
+
path,
|
|
345
|
+
cause: error
|
|
346
|
+
})
|
|
347
|
+
);
|
|
348
|
+
}).on("end", () => {
|
|
349
|
+
if (settled) return;
|
|
350
|
+
if (declaredSize !== void 0 && written !== declaredSize) {
|
|
351
|
+
this.failArchive(
|
|
352
|
+
new ZipSafetyError(
|
|
353
|
+
"size-mismatch",
|
|
354
|
+
`ZIP import: archive member size does not match its metadata: ${path}.`,
|
|
355
|
+
{ path, actual: written }
|
|
356
|
+
)
|
|
357
|
+
);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const actualCrc = (crc ^ 4294967295) >>> 0;
|
|
361
|
+
if (expectedCrc !== void 0 && actualCrc !== expectedCrc >>> 0) {
|
|
362
|
+
this.failArchive(
|
|
363
|
+
new ZipSafetyError("crc-mismatch", `ZIP import: CRC32 mismatch for ${path}.`, {
|
|
364
|
+
path
|
|
365
|
+
})
|
|
366
|
+
);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
settled = true;
|
|
370
|
+
this.activeReads.delete(controller);
|
|
371
|
+
resolve(output.byteLength === written ? output : output.slice(0, written));
|
|
372
|
+
}).resume();
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
assertChunkAllowed(entry, nextEntryBytes, chunkBytes, effectiveEntryLimit) {
|
|
376
|
+
if (entry.declaredSize !== void 0 && nextEntryBytes > entry.declaredSize) {
|
|
377
|
+
throw new ZipSafetyError(
|
|
378
|
+
"size-mismatch",
|
|
379
|
+
`ZIP import: archive member size does not match its metadata: ${entry.path}.`,
|
|
380
|
+
{ path: entry.path, actual: nextEntryBytes }
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
if (effectiveEntryLimit < this.limits.maxUncompressedBytes && nextEntryBytes > effectiveEntryLimit) {
|
|
384
|
+
throw entryTooLargeError(entry.path, nextEntryBytes, effectiveEntryLimit);
|
|
385
|
+
}
|
|
386
|
+
const nextTotal = this.emittedBytes + chunkBytes;
|
|
387
|
+
if (nextTotal > this.limits.maxUncompressedBytes) {
|
|
388
|
+
throw archiveTooLargeError(nextTotal, this.limits.maxUncompressedBytes);
|
|
389
|
+
}
|
|
390
|
+
if (nextEntryBytes > effectiveEntryLimit) {
|
|
391
|
+
throw entryTooLargeError(entry.path, nextEntryBytes, effectiveEntryLimit);
|
|
392
|
+
}
|
|
393
|
+
const ratio = entry.compressedSize === void 0 ? void 0 : compressionRatio(nextEntryBytes, entry.compressedSize);
|
|
394
|
+
if (ratio !== void 0 && ratio > this.limits.maxCompressionRatio) {
|
|
395
|
+
throw compressionRatioError(entry.path, ratio, this.limits.maxCompressionRatio);
|
|
396
|
+
}
|
|
397
|
+
this.emittedBytes = nextTotal;
|
|
398
|
+
}
|
|
399
|
+
failArchive(error) {
|
|
400
|
+
if (this.terminalError) return;
|
|
401
|
+
this.terminalError = error;
|
|
402
|
+
for (const active of [...this.activeReads]) active.stop(error);
|
|
403
|
+
}
|
|
404
|
+
throwIfFailed() {
|
|
405
|
+
if (this.terminalError) throw this.terminalError;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
async function openBoundedZipArchive(data, limits = {}) {
|
|
409
|
+
const resolved = resolveZipSafetyLimits(limits);
|
|
410
|
+
let prepared;
|
|
411
|
+
try {
|
|
412
|
+
prepared = await prepareZipInput(data);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
throw invalidArchiveError(error);
|
|
415
|
+
}
|
|
416
|
+
if (prepared.bytes) {
|
|
417
|
+
const declaredEntries = declaredZipEntryCount(prepared.bytes);
|
|
418
|
+
if (declaredEntries !== void 0 && declaredEntries > resolved.maxEntries) {
|
|
419
|
+
throw tooManyEntriesError(declaredEntries, resolved.maxEntries);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
let zip;
|
|
423
|
+
try {
|
|
424
|
+
zip = await JSZip.loadAsync(prepared.input);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
throw invalidArchiveError(error);
|
|
427
|
+
}
|
|
428
|
+
return createBoundedZipArchive(zip, resolved);
|
|
429
|
+
}
|
|
430
|
+
function invalidArchiveError(cause) {
|
|
431
|
+
return new ZipSafetyError("invalid-archive", "ZIP import: archive is not readable.", { cause });
|
|
432
|
+
}
|
|
433
|
+
function createBoundedZipArchive(zip, limits = {}) {
|
|
434
|
+
const resolved = resolveZipSafetyLimits(limits);
|
|
435
|
+
const entries = validateZipArchive(zip, resolved);
|
|
436
|
+
return new JsZipBoundedArchive(zip, entries, resolved);
|
|
437
|
+
}
|
|
438
|
+
function asZipSafetyError(error, path) {
|
|
439
|
+
if (error instanceof ZipSafetyError) return error;
|
|
440
|
+
return new ZipSafetyError(
|
|
441
|
+
"decompression-failed",
|
|
442
|
+
`ZIP import: could not decompress ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
443
|
+
{ path, cause: error }
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
function growOutput(current, required, declaredSize, limit) {
|
|
447
|
+
if (required <= current.byteLength) return current;
|
|
448
|
+
const maximum = Math.min(declaredSize ?? limit, limit);
|
|
449
|
+
const grownSize = current.byteLength === 0 ? required : current.byteLength * 2;
|
|
450
|
+
const nextSize = Math.min(maximum, Math.max(required, grownSize));
|
|
451
|
+
const next = new Uint8Array(nextSize);
|
|
452
|
+
next.set(current);
|
|
453
|
+
return next;
|
|
454
|
+
}
|
|
455
|
+
var crcTable;
|
|
456
|
+
function updateCrc32(crc, bytes) {
|
|
457
|
+
const table = crcTable ?? (crcTable = makeCrcTable());
|
|
458
|
+
let next = crc >>> 0;
|
|
459
|
+
for (const byte of bytes) next = table[(next ^ byte) & 255] ^ next >>> 8;
|
|
460
|
+
return next >>> 0;
|
|
461
|
+
}
|
|
462
|
+
function makeCrcTable() {
|
|
463
|
+
const table = new Uint32Array(256);
|
|
464
|
+
for (let n = 0; n < table.length; n++) {
|
|
465
|
+
let value = n;
|
|
466
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
467
|
+
value = (value & 1) !== 0 ? 3988292384 ^ value >>> 1 : value >>> 1;
|
|
468
|
+
}
|
|
469
|
+
table[n] = value >>> 0;
|
|
470
|
+
}
|
|
471
|
+
return table;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export {
|
|
475
|
+
ZipSafetyError,
|
|
476
|
+
assertSafeZipPath,
|
|
477
|
+
openBoundedZipArchive
|
|
478
|
+
};
|
|
479
|
+
//# sourceMappingURL=chunk-H7T5DXUU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/zipLimits.ts","../src/shared/boundedZipArchive.ts","../src/shared/zipEntryCount.ts"],"sourcesContent":["/** ZIP safety policy, structured errors, path checks, and metadata validation. */\n\nimport type JSZip from 'jszip';\n\nexport interface ZipSafetyLimits {\n /** Maximum number of archive records, including directories. Default: 10,000. */\n maxEntries?: number;\n /** Maximum uncompressed bytes for one member. Defaults to maxUncompressedBytes. */\n maxEntryUncompressedBytes?: number;\n /** Maximum total uncompressed bytes across the archive. Default: 512 MiB. */\n maxUncompressedBytes?: number;\n /** Maximum uncompressed/compressed ratio for a member. Default: 1,000. */\n maxCompressionRatio?: number;\n}\n\nexport type ZipSafetyErrorCode =\n | 'invalid-limit'\n | 'invalid-archive'\n | 'unsafe-path'\n | 'invalid-entry-metadata'\n | 'too-many-entries'\n | 'entry-too-large'\n | 'archive-too-large'\n | 'compression-ratio-exceeded'\n | 'size-mismatch'\n | 'crc-mismatch'\n | 'decompression-failed';\n\nexport interface ZipSafetyErrorOptions {\n path?: string;\n limit?: number;\n actual?: number;\n cause?: unknown;\n}\n\n/** A stable, machine-readable error raised while validating or reading a ZIP. */\nexport class ZipSafetyError extends Error {\n readonly code: ZipSafetyErrorCode;\n readonly path?: string;\n readonly limit?: number;\n readonly actual?: number;\n\n constructor(code: ZipSafetyErrorCode, message: string, options: ZipSafetyErrorOptions = {}) {\n super(message);\n this.name = 'ZipSafetyError';\n this.code = code;\n this.path = options.path;\n this.limit = options.limit;\n this.actual = options.actual;\n if (options.cause !== undefined) {\n (this as Error & { cause?: unknown }).cause = options.cause;\n }\n }\n}\n\nexport interface ValidatedZipEntry {\n path: string;\n entry: JSZip.JSZipObject;\n declaredSize?: number;\n compressedSize?: number;\n crc32?: number;\n}\n\nexport interface ResolvedZipSafetyLimits {\n maxEntries: number;\n maxEntryUncompressedBytes: number;\n maxUncompressedBytes: number;\n maxCompressionRatio: number;\n}\n\nexport const DEFAULT_MAX_ZIP_ENTRIES = 10_000;\nexport const DEFAULT_MAX_ZIP_UNCOMPRESSED_BYTES = 512 * 1024 * 1024;\nexport const DEFAULT_MAX_ZIP_COMPRESSION_RATIO = 1_000;\n\n/**\n * Validate central-directory metadata before any member is decompressed.\n * JSZip normalizes traversal names, so inspect `unsafeOriginalName` too.\n */\nexport function validateZipArchive(zip: JSZip, limits: ZipSafetyLimits = {}): ValidatedZipEntry[] {\n const resolved = resolveZipSafetyLimits(limits);\n const files: ValidatedZipEntry[] = [];\n let memberCount = 0;\n let declaredTotal = 0;\n let oversizedEntry: { path: string; actual: number; limit: number } | undefined;\n\n zip.forEach((relativePath, entry) => {\n memberCount++;\n if (entry.dir) return;\n const originalPath = originalEntryPath(entry, relativePath);\n if (!isSafeArchivePath(originalPath)) {\n throw new ZipSafetyError(\n 'unsafe-path',\n `ZIP import: archive contains an unsafe path: ${originalPath}.`,\n { path: originalPath },\n );\n }\n\n const metadata = compressedMetadata(entry, originalPath);\n if (\n metadata.uncompressedSize !== undefined &&\n metadata.uncompressedSize > resolved.maxEntryUncompressedBytes\n ) {\n oversizedEntry ??= {\n path: originalPath,\n actual: metadata.uncompressedSize,\n limit: resolved.maxEntryUncompressedBytes,\n };\n }\n\n const ratio =\n metadata.uncompressedSize === undefined || metadata.compressedSize === undefined\n ? undefined\n : compressionRatio(metadata.uncompressedSize, metadata.compressedSize);\n if (ratio !== undefined && ratio > resolved.maxCompressionRatio) {\n throw compressionRatioError(originalPath, ratio, resolved.maxCompressionRatio);\n }\n\n declaredTotal += metadata.uncompressedSize ?? 0;\n if (!Number.isSafeInteger(declaredTotal)) throw invalidMetadataError(originalPath);\n files.push({\n path: relativePath,\n entry,\n ...(metadata.uncompressedSize !== undefined\n ? { declaredSize: metadata.uncompressedSize }\n : {}),\n ...(metadata.compressedSize !== undefined ? { compressedSize: metadata.compressedSize } : {}),\n ...(metadata.crc32 !== undefined ? { crc32: metadata.crc32 } : {}),\n });\n });\n\n if (memberCount > resolved.maxEntries) {\n throw tooManyEntriesError(memberCount, resolved.maxEntries);\n }\n if (declaredTotal > resolved.maxUncompressedBytes) {\n throw archiveTooLargeError(declaredTotal, resolved.maxUncompressedBytes);\n }\n if (oversizedEntry) {\n throw entryTooLargeError(oversizedEntry.path, oversizedEntry.actual, oversizedEntry.limit);\n }\n return files;\n}\n\n/** Reject a path that could be interpreted outside an archive's logical root. */\nexport function assertSafeZipPath(path: string): void {\n if (!isSafeArchivePath(path)) {\n throw new ZipSafetyError('unsafe-path', `ZIP archive path is unsafe: ${path}.`, { path });\n }\n}\n\nexport function resolveZipSafetyLimits(limits: ZipSafetyLimits): ResolvedZipSafetyLimits {\n const maxEntries = limits.maxEntries ?? DEFAULT_MAX_ZIP_ENTRIES;\n const maxUncompressedBytes = limits.maxUncompressedBytes ?? DEFAULT_MAX_ZIP_UNCOMPRESSED_BYTES;\n const maxEntryUncompressedBytes = limits.maxEntryUncompressedBytes ?? maxUncompressedBytes;\n const maxCompressionRatio = limits.maxCompressionRatio ?? DEFAULT_MAX_ZIP_COMPRESSION_RATIO;\n\n assertNonNegativeInteger('maxEntries', maxEntries);\n assertNonNegativeFinite('maxUncompressedBytes', maxUncompressedBytes);\n assertNonNegativeFinite('maxEntryUncompressedBytes', maxEntryUncompressedBytes);\n if (!Number.isFinite(maxCompressionRatio) || maxCompressionRatio < 1) {\n throw invalidLimitError(\n 'maxCompressionRatio must be a finite number greater than or equal to 1',\n );\n }\n return {\n maxEntries,\n maxEntryUncompressedBytes,\n maxUncompressedBytes,\n maxCompressionRatio,\n };\n}\n\nexport function assertNonNegativeFinite(name: string, value: number): void {\n if (!Number.isFinite(value) || value < 0) {\n throw invalidLimitError(`${name} must be a non-negative finite number`);\n }\n}\n\nexport function entryTooLargeError(path: string, actual: number, limit: number): ZipSafetyError {\n return new ZipSafetyError(\n 'entry-too-large',\n `ZIP import: archive member ${path} exceeds ${limit} byte per-entry limit.`,\n { path, limit, actual },\n );\n}\n\nexport function tooManyEntriesError(actual: number, limit: number): ZipSafetyError {\n return new ZipSafetyError(\n 'too-many-entries',\n `ZIP import: archive has ${actual} files; limit is ${limit}.`,\n { limit, actual },\n );\n}\n\nexport function archiveTooLargeError(actual: number, limit: number): ZipSafetyError {\n return new ZipSafetyError(\n 'archive-too-large',\n `ZIP import: uncompressed content exceeds ${limit} byte limit.`,\n { limit, actual },\n );\n}\n\nexport function compressionRatioError(path: string, actual: number, limit: number): ZipSafetyError {\n return new ZipSafetyError(\n 'compression-ratio-exceeded',\n `ZIP import: archive member ${path} exceeds ${limit}:1 compression-ratio limit.`,\n { path, limit, actual },\n );\n}\n\nexport function compressionRatio(uncompressedBytes: number, compressedBytes: number): number {\n if (uncompressedBytes === 0) return 0;\n if (compressedBytes === 0) return Number.POSITIVE_INFINITY;\n return uncompressedBytes / compressedBytes;\n}\n\nfunction assertNonNegativeInteger(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0) {\n throw invalidLimitError(`${name} must be a non-negative integer`);\n }\n}\n\nfunction invalidLimitError(detail: string): ZipSafetyError {\n return new ZipSafetyError('invalid-limit', `ZIP import: ${detail}.`);\n}\n\nfunction originalEntryPath(entry: JSZip.JSZipObject, relativePath: string): string {\n return (\n (\n entry as JSZip.JSZipObject & {\n unsafeOriginalName?: string;\n }\n ).unsafeOriginalName ?? relativePath\n );\n}\n\nfunction compressedMetadata(\n entry: JSZip.JSZipObject,\n path: string,\n): { uncompressedSize?: number; compressedSize?: number; crc32?: number } {\n const data = (\n entry as unknown as {\n _data?: { uncompressedSize?: number; compressedSize?: number; crc32?: number };\n }\n )._data;\n const uncompressedSize = data?.uncompressedSize;\n const compressedSize = data?.compressedSize;\n const crc32 = data?.crc32;\n if (\n (uncompressedSize !== undefined && !isValidMetadataInteger(uncompressedSize)) ||\n (compressedSize !== undefined && !isValidMetadataInteger(compressedSize)) ||\n (crc32 !== undefined && !Number.isInteger(crc32))\n ) {\n throw invalidMetadataError(path);\n }\n return {\n ...(uncompressedSize !== undefined ? { uncompressedSize } : {}),\n ...(compressedSize !== undefined ? { compressedSize } : {}),\n ...(crc32 !== undefined ? { crc32 } : {}),\n };\n}\n\nfunction isValidMetadataInteger(value: number): boolean {\n return Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction invalidMetadataError(path: string): ZipSafetyError {\n return new ZipSafetyError(\n 'invalid-entry-metadata',\n `ZIP import: archive member has invalid size or checksum metadata: ${path}.`,\n { path },\n );\n}\n\nfunction isSafeArchivePath(path: string): boolean {\n const segments = path.split('/');\n return !(\n path.startsWith('/') ||\n /^[a-zA-Z]:[\\\\/]/.test(path) ||\n path.includes('\\\\') ||\n Array.from(path).some((character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n return codePoint < 0x20 || codePoint === 0x7f;\n }) ||\n segments.some((segment) => segment === '' || segment === '.' || segment === '..')\n );\n}\n","/** Incremental JSZip member reads governed by one shared archive budget. */\n\nimport JSZip from 'jszip';\nimport {\n archiveTooLargeError,\n assertNonNegativeFinite,\n compressionRatio,\n compressionRatioError,\n entryTooLargeError,\n resolveZipSafetyLimits,\n validateZipArchive,\n tooManyEntriesError,\n ZipSafetyError,\n type ResolvedZipSafetyLimits,\n type ValidatedZipEntry,\n type ZipSafetyErrorCode,\n type ZipSafetyLimits,\n} from './zipLimits.js';\nimport { declaredZipEntryCount, prepareZipInput, type ZipInput } from './zipEntryCount.js';\n\ninterface ActiveZipRead {\n stop(error: ZipSafetyError): void;\n}\n\ninterface ZipChunkStream {\n on(event: 'data', callback: (chunk: Uint8Array) => void): ZipChunkStream;\n on(event: 'error', callback: (error: unknown) => void): ZipChunkStream;\n on(event: 'end', callback: () => void): ZipChunkStream;\n pause(): ZipChunkStream;\n resume(): ZipChunkStream;\n}\n\ninterface StreamableZipObject {\n internalStream(type: 'uint8array'): ZipChunkStream;\n}\n\n/**\n * A JSZip archive whose member reads share one actual-byte budget.\n *\n * JSZip's promise API accumulates a complete inflated member before returning.\n * This wrapper instead consumes `internalStream('uint8array')`, checks every\n * emitted chunk, and pauses every active stream as soon as any limit fails.\n * Completed reads are cached so repeated OOXML part access is neither inflated\n * nor charged twice.\n */\nexport interface BoundedZipArchive {\n readonly zip: JSZip;\n readonly entries: readonly ValidatedZipEntry[];\n readonly emittedUncompressedBytes: number;\n read(path: string, maxBytes?: number): Promise<Uint8Array | null>;\n readText(path: string, maxBytes?: number): Promise<string | null>;\n release(path: string): void;\n}\n\nclass JsZipBoundedArchive implements BoundedZipArchive {\n readonly zip: JSZip;\n readonly entries: readonly ValidatedZipEntry[];\n\n private readonly limits: ResolvedZipSafetyLimits;\n private readonly entriesByPath: ReadonlyMap<string, ValidatedZipEntry>;\n private readonly reads = new Map<string, Promise<Uint8Array>>();\n private readonly activeReads = new Set<ActiveZipRead>();\n private terminalError?: ZipSafetyError;\n private emittedBytes = 0;\n\n constructor(zip: JSZip, entries: readonly ValidatedZipEntry[], limits: ResolvedZipSafetyLimits) {\n this.zip = zip;\n this.entries = entries;\n this.limits = limits;\n this.entriesByPath = new Map(entries.map((entry) => [entry.path, entry]));\n }\n\n /** Actual uncompressed bytes accepted from JSZip streams so far. */\n get emittedUncompressedBytes(): number {\n return this.emittedBytes;\n }\n\n /** Read and cache one member, returning null when it does not exist. */\n async read(path: string, maxBytes?: number): Promise<Uint8Array | null> {\n this.throwIfFailed();\n if (maxBytes !== undefined) assertNonNegativeFinite('member read limit', maxBytes);\n const entry = this.entriesByPath.get(path);\n if (!entry) return null;\n\n let pending = this.reads.get(path);\n if (!pending) {\n pending = this.streamEntry(entry, maxBytes);\n this.reads.set(path, pending);\n }\n const bytes = await pending;\n if (maxBytes !== undefined && bytes.byteLength > maxBytes) {\n const error = entryTooLargeError(path, bytes.byteLength, maxBytes);\n this.failArchive(error);\n throw error;\n }\n return bytes;\n }\n\n /** Read one UTF-8 text member, returning null when it does not exist. */\n async readText(path: string, maxBytes?: number): Promise<string | null> {\n const bytes = await this.read(path, maxBytes);\n return bytes ? new TextDecoder().decode(bytes) : null;\n }\n\n /** Release a cached member when a sequential consumer has taken ownership. */\n release(path: string): void {\n this.reads.delete(path);\n }\n\n private streamEntry(metadata: ValidatedZipEntry, readLimit?: number): Promise<Uint8Array> {\n const { path, entry, declaredSize, crc32: expectedCrc } = metadata;\n const effectiveEntryLimit = Math.min(\n this.limits.maxEntryUncompressedBytes,\n readLimit ?? Number.POSITIVE_INFINITY,\n );\n // Never trust a declaration enough to allocate it up front. A forged\n // 511 MiB size must not force 511 MiB of memory for a tiny member.\n let output: Uint8Array = new Uint8Array(0);\n let stream: ZipChunkStream;\n try {\n stream = (entry as unknown as StreamableZipObject).internalStream('uint8array');\n } catch (error: unknown) {\n const safeError = asZipSafetyError(error, path);\n this.failArchive(safeError);\n return Promise.reject(safeError);\n }\n\n return new Promise<Uint8Array>((resolve, reject) => {\n let settled = false;\n let written = 0;\n let crc = 0xffffffff;\n\n const controller: ActiveZipRead = {\n stop: (error) => {\n if (settled) return;\n settled = true;\n this.activeReads.delete(controller);\n // Public JSZip cancellation is cooperative. It prevents another\n // compressed-input tick; data already emitted synchronously by the\n // current tick is ignored because `settled` is now true.\n try {\n stream.pause();\n } catch {\n // Reject with the original safety failure even if a malformed\n // third-party stream cannot be paused cleanly.\n }\n reject(error);\n },\n };\n this.activeReads.add(controller);\n\n stream\n .on('data', (chunk: Uint8Array) => {\n if (settled) return;\n const nextWritten = written + chunk.byteLength;\n try {\n this.assertChunkAllowed(metadata, nextWritten, chunk.byteLength, effectiveEntryLimit);\n output = growOutput(output, nextWritten, declaredSize, effectiveEntryLimit);\n output.set(chunk, written);\n written = nextWritten;\n crc = updateCrc32(crc, chunk);\n } catch (error: unknown) {\n this.failArchive(asZipSafetyError(error, path));\n }\n })\n .on('error', (error: unknown) => {\n if (settled) return;\n const message = error instanceof Error ? error.message : String(error);\n const code: ZipSafetyErrorCode = message.includes('uncompressed data size mismatch')\n ? 'size-mismatch'\n : 'decompression-failed';\n this.failArchive(\n new ZipSafetyError(code, `ZIP import: could not decompress ${path}: ${message}`, {\n path,\n cause: error,\n }),\n );\n })\n .on('end', () => {\n if (settled) return;\n if (declaredSize !== undefined && written !== declaredSize) {\n this.failArchive(\n new ZipSafetyError(\n 'size-mismatch',\n `ZIP import: archive member size does not match its metadata: ${path}.`,\n { path, actual: written },\n ),\n );\n return;\n }\n const actualCrc = (crc ^ 0xffffffff) >>> 0;\n if (expectedCrc !== undefined && actualCrc !== expectedCrc >>> 0) {\n this.failArchive(\n new ZipSafetyError('crc-mismatch', `ZIP import: CRC32 mismatch for ${path}.`, {\n path,\n }),\n );\n return;\n }\n\n settled = true;\n this.activeReads.delete(controller);\n resolve(output.byteLength === written ? output : output.slice(0, written));\n })\n .resume();\n });\n }\n\n private assertChunkAllowed(\n entry: ValidatedZipEntry,\n nextEntryBytes: number,\n chunkBytes: number,\n effectiveEntryLimit: number,\n ): void {\n if (entry.declaredSize !== undefined && nextEntryBytes > entry.declaredSize) {\n throw new ZipSafetyError(\n 'size-mismatch',\n `ZIP import: archive member size does not match its metadata: ${entry.path}.`,\n { path: entry.path, actual: nextEntryBytes },\n );\n }\n if (\n effectiveEntryLimit < this.limits.maxUncompressedBytes &&\n nextEntryBytes > effectiveEntryLimit\n ) {\n throw entryTooLargeError(entry.path, nextEntryBytes, effectiveEntryLimit);\n }\n\n const nextTotal = this.emittedBytes + chunkBytes;\n if (nextTotal > this.limits.maxUncompressedBytes) {\n throw archiveTooLargeError(nextTotal, this.limits.maxUncompressedBytes);\n }\n if (nextEntryBytes > effectiveEntryLimit) {\n throw entryTooLargeError(entry.path, nextEntryBytes, effectiveEntryLimit);\n }\n\n const ratio =\n entry.compressedSize === undefined\n ? undefined\n : compressionRatio(nextEntryBytes, entry.compressedSize);\n if (ratio !== undefined && ratio > this.limits.maxCompressionRatio) {\n throw compressionRatioError(entry.path, ratio, this.limits.maxCompressionRatio);\n }\n this.emittedBytes = nextTotal;\n }\n\n private failArchive(error: ZipSafetyError): void {\n if (this.terminalError) return;\n this.terminalError = error;\n for (const active of [...this.activeReads]) active.stop(error);\n }\n\n private throwIfFailed(): void {\n if (this.terminalError) throw this.terminalError;\n }\n}\n\n/** Load with JSZip, validate metadata, and create a shared bounded read session. */\nexport async function openBoundedZipArchive(\n data: ZipInput,\n limits: ZipSafetyLimits = {},\n): Promise<BoundedZipArchive> {\n const resolved = resolveZipSafetyLimits(limits);\n let prepared: { input: ZipInput; bytes?: Uint8Array };\n try {\n prepared = await prepareZipInput(data);\n } catch (error: unknown) {\n throw invalidArchiveError(error);\n }\n if (prepared.bytes) {\n const declaredEntries = declaredZipEntryCount(prepared.bytes);\n if (declaredEntries !== undefined && declaredEntries > resolved.maxEntries) {\n throw tooManyEntriesError(declaredEntries, resolved.maxEntries);\n }\n }\n let zip: JSZip;\n try {\n zip = await JSZip.loadAsync(prepared.input);\n } catch (error: unknown) {\n throw invalidArchiveError(error);\n }\n return createBoundedZipArchive(zip, resolved);\n}\n\nfunction invalidArchiveError(cause: unknown): ZipSafetyError {\n return new ZipSafetyError('invalid-archive', 'ZIP import: archive is not readable.', { cause });\n}\n\n/** Wrap an existing JSZip instance in the same validation and shared budget. */\nexport function createBoundedZipArchive(\n zip: JSZip,\n limits: ZipSafetyLimits = {},\n): BoundedZipArchive {\n const resolved = resolveZipSafetyLimits(limits);\n const entries = validateZipArchive(zip, resolved);\n return new JsZipBoundedArchive(zip, entries, resolved);\n}\n\nfunction asZipSafetyError(error: unknown, path: string): ZipSafetyError {\n if (error instanceof ZipSafetyError) return error;\n return new ZipSafetyError(\n 'decompression-failed',\n `ZIP import: could not decompress ${path}: ${error instanceof Error ? error.message : String(error)}`,\n { path, cause: error },\n );\n}\n\nfunction growOutput(\n current: Uint8Array,\n required: number,\n declaredSize: number | undefined,\n limit: number,\n): Uint8Array {\n if (required <= current.byteLength) return current;\n const maximum = Math.min(declaredSize ?? limit, limit);\n const grownSize = current.byteLength === 0 ? required : current.byteLength * 2;\n const nextSize = Math.min(maximum, Math.max(required, grownSize));\n const next = new Uint8Array(nextSize);\n next.set(current);\n return next;\n}\n\nlet crcTable: Uint32Array | undefined;\n\nfunction updateCrc32(crc: number, bytes: Uint8Array): number {\n const table = crcTable ?? (crcTable = makeCrcTable());\n let next = crc >>> 0;\n for (const byte of bytes) next = table[(next ^ byte) & 0xff]! ^ (next >>> 8);\n return next >>> 0;\n}\n\nfunction makeCrcTable(): Uint32Array {\n const table = new Uint32Array(256);\n for (let n = 0; n < table.length; n++) {\n let value = n;\n for (let bit = 0; bit < 8; bit++) {\n value = (value & 1) !== 0 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;\n }\n table[n] = value >>> 0;\n }\n return table;\n}\n","/** Narrow EOCD preflight for rejecting record-count bombs before JSZip allocation. */\n\nconst END_OF_CENTRAL_DIRECTORY = 0x06054b50;\nconst ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50;\nconst ZIP64_END_LOCATOR = 0x07064b50;\nconst UINT16_MAX = 0xffff;\nconst MIN_EOCD_BYTES = 22;\nconst MAX_ZIP_COMMENT_BYTES = 0xffff;\n\nexport type ZipInput = ArrayBuffer | Uint8Array | Blob;\n\n/** Materialize Blob input once when possible so preflight bytes feed JSZip too. */\nexport async function prepareZipInput(\n data: ZipInput,\n): Promise<{ input: ZipInput; bytes?: Uint8Array }> {\n if (ArrayBuffer.isView(data)) {\n const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n return { input: bytes, bytes };\n }\n if (!isBlobLike(data)) {\n const bytes = new Uint8Array(data);\n return { input: bytes, bytes };\n }\n\n const bytes = await readBlobBytes(data);\n return bytes ? { input: bytes, bytes } : { input: data };\n}\n\n/**\n * Read the declared total central-directory record count when unambiguous.\n * Returns undefined for absent, trailing, malformed, or unsupported metadata;\n * JSZip remains authoritative in those cases.\n */\nexport function declaredZipEntryCount(bytes: Uint8Array): number | undefined {\n const eocdOffset = findEocd(bytes);\n if (eocdOffset === undefined) return undefined;\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const count = view.getUint16(eocdOffset + 10, true);\n if (count !== UINT16_MAX) return count;\n return readZip64EntryCount(view, eocdOffset);\n}\n\nfunction findEocd(bytes: Uint8Array): number | undefined {\n if (bytes.byteLength < MIN_EOCD_BYTES) return undefined;\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const earliest = Math.max(0, bytes.byteLength - MIN_EOCD_BYTES - MAX_ZIP_COMMENT_BYTES);\n for (let offset = bytes.byteLength - MIN_EOCD_BYTES; offset >= earliest; offset--) {\n if (view.getUint32(offset, true) !== END_OF_CENTRAL_DIRECTORY) continue;\n const commentLength = view.getUint16(offset + 20, true);\n if (offset + MIN_EOCD_BYTES + commentLength === bytes.byteLength) return offset;\n }\n return undefined;\n}\n\nfunction readZip64EntryCount(view: DataView, eocdOffset: number): number | undefined {\n const locatorOffset = eocdOffset - 20;\n if (locatorOffset < 0 || view.getUint32(locatorOffset, true) !== ZIP64_END_LOCATOR) {\n return undefined;\n }\n const recordOffset = readSafeUint64(view, locatorOffset + 8);\n if (\n recordOffset === undefined ||\n recordOffset < 0 ||\n recordOffset + 40 > view.byteLength ||\n view.getUint32(recordOffset, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY\n ) {\n return undefined;\n }\n return readSafeUint64(view, recordOffset + 32);\n}\n\nfunction readSafeUint64(view: DataView, offset: number): number | undefined {\n if (offset < 0 || offset + 8 > view.byteLength) return undefined;\n const low = view.getUint32(offset, true);\n const high = view.getUint32(offset + 4, true);\n const value = high * 0x1_0000_0000 + low;\n return Number.isSafeInteger(value) ? value : undefined;\n}\n\nfunction isBlobLike(data: ZipInput): data is Blob {\n const candidate = data as Blob;\n return typeof candidate.size === 'number' && typeof candidate.slice === 'function';\n}\n\nasync function readBlobBytes(blob: Blob): Promise<Uint8Array | undefined> {\n const arrayBuffer = (blob as Blob & { arrayBuffer?: () => Promise<ArrayBuffer> }).arrayBuffer;\n if (typeof arrayBuffer === 'function') {\n return new Uint8Array(await arrayBuffer.call(blob));\n }\n if (typeof FileReader === 'undefined') return undefined;\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(new Uint8Array(reader.result as ArrayBuffer));\n reader.onerror = () => reject(reader.error ?? new Error('Could not read ZIP blob.'));\n reader.readAsArrayBuffer(blob);\n });\n}\n"],"mappings":"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","names":["bytes"]}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
extractPlainText
|
|
3
3
|
} from "./chunk-PN52A5AA.js";
|
|
4
|
+
import {
|
|
5
|
+
createPackage
|
|
6
|
+
} from "./chunk-XTLQXR4U.js";
|
|
7
|
+
import {
|
|
8
|
+
escapeXml,
|
|
9
|
+
xmlDeclaration
|
|
10
|
+
} from "./chunk-U4MRIFKL.js";
|
|
4
11
|
import {
|
|
5
12
|
CONTENT_TYPE_XLSX_STYLES,
|
|
6
13
|
CONTENT_TYPE_XLSX_WORKBOOK,
|
|
@@ -10,15 +17,10 @@ import {
|
|
|
10
17
|
REL_OFFICE_DOCUMENT,
|
|
11
18
|
REL_STYLES,
|
|
12
19
|
REL_WORKSHEET,
|
|
13
|
-
createPackage,
|
|
14
20
|
getPartRelationships,
|
|
15
21
|
getPartXml,
|
|
16
22
|
openPackage
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import {
|
|
19
|
-
escapeXml,
|
|
20
|
-
xmlDeclaration
|
|
21
|
-
} from "./chunk-U4MRIFKL.js";
|
|
23
|
+
} from "./chunk-4HJ6AK2Q.js";
|
|
22
24
|
|
|
23
25
|
// src/xlsx/index.ts
|
|
24
26
|
import { markdownToDoc } from "@bendyline/squisq/doc";
|
|
@@ -115,7 +117,7 @@ function gridToTable(grid) {
|
|
|
115
117
|
return { type: "table", children: rows };
|
|
116
118
|
}
|
|
117
119
|
async function xlsxToMarkdownDoc(data, options = {}) {
|
|
118
|
-
const pkg = await openPackage(data);
|
|
120
|
+
const pkg = await openPackage(data, options);
|
|
119
121
|
const sheets = await readSheets(pkg);
|
|
120
122
|
const shared = await readSharedStrings(pkg);
|
|
121
123
|
let selected = sheets;
|
|
@@ -270,4 +272,4 @@ export {
|
|
|
270
272
|
docToXlsx,
|
|
271
273
|
xlsxToDoc
|
|
272
274
|
};
|
|
273
|
-
//# sourceMappingURL=chunk-
|
|
275
|
+
//# sourceMappingURL=chunk-HBSCRS5P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/xlsx/index.ts","../src/xlsx/import.ts","../src/xlsx/export.ts"],"sourcesContent":["/**\n * @bendyline/squisq-formats XLSX Module\n *\n * Excel .xlsx support via SpreadsheetML and the shared ooxml/ infrastructure.\n * Both directions are implemented: import (`xlsxToMarkdownDoc` / `xlsxToDoc`)\n * turns each worksheet grid into a markdown table; export (`markdownDocToXlsx`\n * / `docToXlsx`) turns each markdown table into a worksheet. Export is\n * tables-only fidelity — non-table content is dropped (headings only survive\n * as sheet names). See export.ts for details.\n *\n * @example\n * ```ts\n * import { xlsxToMarkdownDoc, markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';\n * ```\n */\n\nimport { markdownToDoc } from '@bendyline/squisq/doc';\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport { type XlsxImportOptions, xlsxToMarkdownDoc } from './import.js';\n\nexport type { XlsxImportOptions } from './import.js';\nexport { xlsxToMarkdownDoc } from './import.js';\n\nexport type { XlsxExportOptions } from './export.js';\nexport { markdownDocToXlsx, docToXlsx } from './export.js';\n\n/**\n * Convert a .xlsx file to a squisq Doc (via the markdown table model).\n */\nexport async function xlsxToDoc(\n data: ArrayBuffer | Blob,\n options?: XlsxImportOptions,\n): Promise<Doc> {\n return markdownToDoc(await xlsxToMarkdownDoc(data, options));\n}\n","/**\n * XLSX import — SpreadsheetML (.xlsx) → MarkdownDocument.\n *\n * Reuses the shared ooxml/ reader (zip + DOMParser). Reads the workbook's\n * sheet list, resolves each sheet part via relationships, pulls shared strings,\n * and turns each worksheet's cell grid into a markdown table (first row treated\n * as the header). By default every sheet is imported, each preceded by an H1 of\n * the sheet name; pass `options.sheet` (index or name) to import just one.\n */\n\nimport type {\n MarkdownBlockNode,\n MarkdownDocument,\n MarkdownTable,\n MarkdownTableCell,\n MarkdownTableRow,\n} from '@bendyline/squisq/markdown';\nimport { getPartXml, getPartRelationships, openPackage } from '../ooxml/reader.js';\nimport type { OoxmlOpenOptions } from '../ooxml/reader.js';\nimport type { OoxmlPackage } from '../ooxml/types.js';\nimport { NS_R, NS_SML } from '../ooxml/namespaces.js';\n\nexport interface XlsxImportOptions extends OoxmlOpenOptions {\n /** Which sheet to import (0-based index or sheet name). Default: all sheets. */\n sheet?: number | string;\n}\n\ninterface SheetRef {\n name: string;\n path: string;\n}\n\nfunction attrNS(el: Element, ns: string, local: string, fallback: string): string | null {\n return el.getAttributeNS(ns, local) ?? el.getAttribute(fallback);\n}\n\n/** Resolve a relationship target (relative to its part's dir) to a zip path. */\nfunction resolveTarget(baseDir: string, target: string): string {\n if (target.startsWith('/')) return target.replace(/^\\//, '');\n const stack = baseDir ? baseDir.split('/') : [];\n for (const seg of target.split('/')) {\n if (seg === '..') stack.pop();\n else if (seg !== '.') stack.push(seg);\n }\n return stack.join('/');\n}\n\nasync function readSheets(pkg: OoxmlPackage): Promise<SheetRef[]> {\n const wb = await getPartXml(pkg, 'xl/workbook.xml');\n if (!wb) return [];\n const rels = await getPartRelationships(pkg, 'xl/workbook.xml');\n const relById = new Map(rels.map((r) => [r.id, r.target]));\n const out: SheetRef[] = [];\n const sheetEls = wb.getElementsByTagNameNS(NS_SML, 'sheet');\n for (let i = 0; i < sheetEls.length; i++) {\n const el = sheetEls[i]!;\n const name = el.getAttribute('name') ?? `Sheet${i + 1}`;\n const rid = attrNS(el, NS_R, 'id', 'r:id');\n const target = rid ? relById.get(rid) : undefined;\n if (target) out.push({ name, path: resolveTarget('xl', target) });\n }\n return out;\n}\n\nasync function readSharedStrings(pkg: OoxmlPackage): Promise<string[]> {\n const doc = await getPartXml(pkg, 'xl/sharedStrings.xml');\n if (!doc) return [];\n const siEls = doc.getElementsByTagNameNS(NS_SML, 'si');\n const out: string[] = [];\n for (let i = 0; i < siEls.length; i++) out.push(siEls[i]!.textContent ?? '');\n return out;\n}\n\n/** Column letters of a cell ref (\"B7\" → 1, \"AA1\" → 26). */\nfunction colIndex(ref: string): number {\n const m = /^([A-Za-z]+)/.exec(ref);\n if (!m) return 0;\n let n = 0;\n for (const ch of m[1]!.toUpperCase()) n = n * 26 + (ch.charCodeAt(0) - 64);\n return n - 1;\n}\n\nfunction cellText(cell: Element, shared: string[]): string {\n const t = cell.getAttribute('t');\n if (t === 'inlineStr') {\n const is = cell.getElementsByTagNameNS(NS_SML, 't');\n return is.length ? (is[0]!.textContent ?? '') : '';\n }\n const vEls = cell.getElementsByTagNameNS(NS_SML, 'v');\n const v = vEls.length ? (vEls[0]!.textContent ?? '') : '';\n if (t === 's') return shared[Number.parseInt(v, 10)] ?? '';\n if (t === 'b') return v === '1' ? 'TRUE' : 'FALSE';\n return v;\n}\n\nasync function sheetToGrid(pkg: OoxmlPackage, path: string, shared: string[]): Promise<string[][]> {\n const doc = await getPartXml(pkg, path);\n if (!doc) return [];\n const rowEls = doc.getElementsByTagNameNS(NS_SML, 'row');\n const grid: string[][] = [];\n for (let r = 0; r < rowEls.length; r++) {\n const cells = rowEls[r]!.getElementsByTagNameNS(NS_SML, 'c');\n const rowArr: string[] = [];\n for (let c = 0; c < cells.length; c++) {\n const cell = cells[c]!;\n const ref = cell.getAttribute('r');\n const idx = ref ? colIndex(ref) : rowArr.length;\n while (rowArr.length < idx) rowArr.push('');\n rowArr[idx] = cellText(cell, shared);\n }\n grid.push(rowArr);\n }\n return grid;\n}\n\nfunction gridToTable(grid: string[][]): MarkdownTable {\n const maxCols = grid.reduce((m, r) => Math.max(m, r.length), 1);\n const rows: MarkdownTableRow[] = grid.map((cells, rowIdx) => {\n const children: MarkdownTableCell[] = [];\n for (let c = 0; c < maxCols; c++) {\n const value = cells[c] ?? '';\n children.push({\n type: 'tableCell',\n ...(rowIdx === 0 ? { isHeader: true } : {}),\n children: value ? [{ type: 'text', value }] : [],\n });\n }\n return { type: 'tableRow', children };\n });\n return { type: 'table', children: rows };\n}\n\nexport async function xlsxToMarkdownDoc(\n data: ArrayBuffer | Blob,\n options: XlsxImportOptions = {},\n): Promise<MarkdownDocument> {\n const pkg = await openPackage(data, options);\n const sheets = await readSheets(pkg);\n const shared = await readSharedStrings(pkg);\n\n let selected = sheets;\n if (options.sheet !== undefined) {\n const picked =\n typeof options.sheet === 'number'\n ? sheets[options.sheet]\n : sheets.find((s) => s.name === options.sheet);\n selected = picked ? [picked] : [];\n }\n\n const children: MarkdownBlockNode[] = [];\n const single = selected.length === 1 && options.sheet !== undefined;\n for (const sheet of selected) {\n const grid = await sheetToGrid(pkg, sheet.path, shared);\n if (!single) {\n children.push({ type: 'heading', depth: 1, children: [{ type: 'text', value: sheet.name }] });\n }\n if (grid.length > 0) children.push(gridToTable(grid));\n }\n return { type: 'document', children };\n}\n","/**\n * XLSX export — MarkdownDocument → SpreadsheetML (.xlsx).\n *\n * Tables-only fidelity (honestly documented): every `table` node in the\n * markdown AST becomes one worksheet; all other content (headings, prose,\n * lists, images, …) is dropped except that the nearest preceding heading is\n * used to name the sheet. This mirrors the import side (`xlsxToMarkdownDoc`),\n * which turns each worksheet grid back into a markdown table.\n *\n * Cells are emitted as inline strings (`t=\"inlineStr\"`) so no sharedStrings\n * part is needed, except values matching a plain-number pattern, which are\n * emitted as numeric cells. The package is assembled with the shared ooxml/\n * writer (auto-generates `[Content_Types].xml` + `_rels`), so only the\n * SpreadsheetML-specific parts (workbook, worksheets, styles) are written here.\n *\n * @example\n * ```ts\n * import { parseMarkdown } from '@bendyline/squisq/markdown';\n * import { markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';\n *\n * const md = parseMarkdown('# Metrics\\n\\n| A | B |\\n| - | - |\\n| 1 | 2 |');\n * const buffer = await markdownDocToXlsx(md);\n * ```\n */\n\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport { docToMarkdown } from '@bendyline/squisq/doc';\nimport type {\n MarkdownDocument,\n MarkdownBlockNode,\n MarkdownTable,\n MarkdownTableRow,\n} from '@bendyline/squisq/markdown';\n\nimport { createPackage } from '../ooxml/writer.js';\nimport { xmlDeclaration, escapeXml } from '../ooxml/xmlUtils.js';\nimport { extractPlainText } from '../shared/text.js';\nimport {\n NS_SML,\n NS_R,\n REL_OFFICE_DOCUMENT,\n REL_STYLES,\n REL_WORKSHEET,\n CONTENT_TYPE_XLSX_WORKBOOK,\n CONTENT_TYPE_XLSX_WORKSHEET,\n CONTENT_TYPE_XLSX_STYLES,\n} from '../ooxml/namespaces.js';\n\n/**\n * Options for XLSX export.\n */\nexport interface XlsxExportOptions {\n /** Workbook title (written to core properties). */\n title?: string;\n /** Workbook author (written to core properties). */\n author?: string;\n /** Prefix used for auto-named sheets when no heading precedes a table. Default: \"Sheet\". */\n sheetNamePrefix?: string;\n}\n\ninterface SheetData {\n name: string;\n /** Grid of cell text: first row is the header. */\n grid: string[][];\n}\n\n/** A plain integer or decimal (optionally negative) — emitted as a numeric cell. */\nconst NUMERIC_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Sanitize a candidate name into a valid, unique Excel sheet name.\n * Strips characters Excel forbids (`[]:*?/\\`), caps at 31 chars, and\n * de-duplicates against already-used names by appending 2, 3, …\n */\nfunction sanitizeSheetName(raw: string, used: Set<string>, fallback: string): string {\n let name = raw.replace(/[[\\]:*?/\\\\]/g, '').trim();\n if (name.length === 0) name = fallback;\n name = name.slice(0, 31);\n\n if (!used.has(name)) {\n used.add(name);\n return name;\n }\n // Dedupe: \"Name\", \"Name2\", \"Name3\", … keeping within the 31-char cap.\n for (let n = 2; ; n++) {\n const suffix = String(n);\n const base = name.slice(0, 31 - suffix.length);\n const candidate = `${base}${suffix}`;\n if (!used.has(candidate)) {\n used.add(candidate);\n return candidate;\n }\n }\n}\n\n/** Convert a table node into a grid of plain-text cells. */\nfunction tableToGrid(table: MarkdownTable): string[][] {\n return table.children.map((row: MarkdownTableRow) =>\n row.children.map((cell) => extractPlainText(cell.children)),\n );\n}\n\n/** Walk the document, pairing each table with the nearest preceding heading. */\nfunction collectSheets(doc: MarkdownDocument, prefix: string): SheetData[] {\n const used = new Set<string>();\n const sheets: SheetData[] = [];\n let pendingHeading: string | null = null;\n\n const visit = (nodes: MarkdownBlockNode[]): void => {\n for (const node of nodes) {\n if (node.type === 'heading') {\n pendingHeading = extractPlainText(node.children);\n } else if (node.type === 'table') {\n const fallback = `${prefix}${sheets.length + 1}`;\n const name = sanitizeSheetName(pendingHeading ?? fallback, used, fallback);\n sheets.push({ name, grid: tableToGrid(node) });\n pendingHeading = null;\n }\n }\n };\n\n visit(doc.children);\n return sheets;\n}\n\n/** Column letters for a zero-based index (0 → \"A\", 26 → \"AA\"). */\nfunction columnLetter(index: number): string {\n let n = index + 1;\n let letters = '';\n while (n > 0) {\n const rem = (n - 1) % 26;\n letters = String.fromCharCode(65 + rem) + letters;\n n = Math.floor((n - 1) / 26);\n }\n return letters;\n}\n\nfunction cellXml(value: string, ref: string): string {\n if (NUMERIC_RE.test(value)) {\n return `<c r=\"${ref}\"><v>${escapeXml(value)}</v></c>`;\n }\n return `<c r=\"${ref}\" t=\"inlineStr\"><is><t xml:space=\"preserve\">${escapeXml(value)}</t></is></c>`;\n}\n\nfunction worksheetXml(grid: string[][]): string {\n const rows: string[] = [];\n for (let r = 0; r < grid.length; r++) {\n const cells = grid[r]!;\n const cellsXml = cells.map((value, c) => cellXml(value, `${columnLetter(c)}${r + 1}`)).join('');\n rows.push(`<row r=\"${r + 1}\">${cellsXml}</row>`);\n }\n return (\n `${xmlDeclaration()}\\n` +\n `<worksheet xmlns=\"${NS_SML}\" xmlns:r=\"${NS_R}\">` +\n `<sheetData>${rows.join('')}</sheetData>` +\n `</worksheet>`\n );\n}\n\nfunction workbookXml(sheets: SheetData[]): string {\n const sheetEls = sheets\n .map(\n (sheet, i) =>\n `<sheet name=\"${escapeXml(sheet.name)}\" sheetId=\"${i + 1}\" r:id=\"rId${i + 1}\"/>`,\n )\n .join('');\n return (\n `${xmlDeclaration()}\\n` +\n `<workbook xmlns=\"${NS_SML}\" xmlns:r=\"${NS_R}\">` +\n `<sheets>${sheetEls}</sheets>` +\n `</workbook>`\n );\n}\n\n/** Minimal but valid styles part (Excel expects the referenced collections to exist). */\nfunction stylesXml(): string {\n return (\n `${xmlDeclaration()}\\n` +\n `<styleSheet xmlns=\"${NS_SML}\">` +\n `<fonts count=\"1\"><font><sz val=\"11\"/><name val=\"Calibri\"/></font></fonts>` +\n `<fills count=\"1\"><fill><patternFill patternType=\"none\"/></fill></fills>` +\n `<borders count=\"1\"><border/></borders>` +\n `<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>` +\n `<cellXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/></cellXfs>` +\n `</styleSheet>`\n );\n}\n\n/**\n * Convert a MarkdownDocument to a .xlsx file (tables-only fidelity).\n *\n * Each markdown `table` becomes one worksheet; a document with no tables\n * yields a single empty sheet (a valid, openable file — never throws).\n */\nexport async function markdownDocToXlsx(\n doc: MarkdownDocument,\n options: XlsxExportOptions = {},\n): Promise<ArrayBuffer> {\n const prefix = options.sheetNamePrefix ?? 'Sheet';\n let sheets = collectSheets(doc, prefix);\n\n // Zero tables → one empty sheet so the file is still valid.\n if (sheets.length === 0) {\n sheets = [{ name: `${prefix}1`, grid: [] }];\n }\n\n const pkg = createPackage();\n\n // Worksheets + workbook→worksheet relationships.\n sheets.forEach((sheet, i) => {\n const sheetPath = `xl/worksheets/sheet${i + 1}.xml`;\n pkg.addPart(sheetPath, worksheetXml(sheet.grid), CONTENT_TYPE_XLSX_WORKSHEET);\n pkg.addRelationship('xl/workbook.xml', {\n id: `rId${i + 1}`,\n type: REL_WORKSHEET,\n target: `worksheets/sheet${i + 1}.xml`,\n });\n });\n\n // Styles part (referenced after the sheet relationships).\n pkg.addPart('xl/styles.xml', stylesXml(), CONTENT_TYPE_XLSX_STYLES);\n pkg.addRelationship('xl/workbook.xml', {\n id: `rId${sheets.length + 1}`,\n type: REL_STYLES,\n target: 'styles.xml',\n });\n\n // Workbook + root relationship.\n pkg.addPart('xl/workbook.xml', workbookXml(sheets), CONTENT_TYPE_XLSX_WORKBOOK);\n pkg.addRelationship('', {\n id: 'rId1',\n type: REL_OFFICE_DOCUMENT,\n target: 'xl/workbook.xml',\n });\n\n if (options.title || options.author) {\n pkg.setCoreProperties({\n title: options.title,\n creator: options.author,\n created: new Date().toISOString(),\n modified: new Date().toISOString(),\n });\n }\n\n return pkg.toArrayBuffer();\n}\n\n/**\n * Convert a squisq Doc to a .xlsx file (via the markdown table model).\n */\nexport async function docToXlsx(doc: Doc, options?: XlsxExportOptions): Promise<ArrayBuffer> {\n return markdownDocToXlsx(docToMarkdown(doc), options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,SAAS,qBAAqB;;;ACgB9B,SAAS,OAAO,IAAa,IAAY,OAAe,UAAiC;AACvF,SAAO,GAAG,eAAe,IAAI,KAAK,KAAK,GAAG,aAAa,QAAQ;AACjE;AAGA,SAAS,cAAc,SAAiB,QAAwB;AAC9D,MAAI,OAAO,WAAW,GAAG,EAAG,QAAO,OAAO,QAAQ,OAAO,EAAE;AAC3D,QAAM,QAAQ,UAAU,QAAQ,MAAM,GAAG,IAAI,CAAC;AAC9C,aAAW,OAAO,OAAO,MAAM,GAAG,GAAG;AACnC,QAAI,QAAQ,KAAM,OAAM,IAAI;AAAA,aACnB,QAAQ,IAAK,OAAM,KAAK,GAAG;AAAA,EACtC;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAEA,eAAe,WAAW,KAAwC;AAChE,QAAM,KAAK,MAAM,WAAW,KAAK,iBAAiB;AAClD,MAAI,CAAC,GAAI,QAAO,CAAC;AACjB,QAAM,OAAO,MAAM,qBAAqB,KAAK,iBAAiB;AAC9D,QAAM,UAAU,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACzD,QAAM,MAAkB,CAAC;AACzB,QAAM,WAAW,GAAG,uBAAuB,QAAQ,OAAO;AAC1D,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,KAAK,SAAS,CAAC;AACrB,UAAM,OAAO,GAAG,aAAa,MAAM,KAAK,QAAQ,IAAI,CAAC;AACrD,UAAM,MAAM,OAAO,IAAI,MAAM,MAAM,MAAM;AACzC,UAAM,SAAS,MAAM,QAAQ,IAAI,GAAG,IAAI;AACxC,QAAI,OAAQ,KAAI,KAAK,EAAE,MAAM,MAAM,cAAc,MAAM,MAAM,EAAE,CAAC;AAAA,EAClE;AACA,SAAO;AACT;AAEA,eAAe,kBAAkB,KAAsC;AACrE,QAAM,MAAM,MAAM,WAAW,KAAK,sBAAsB;AACxD,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,QAAQ,IAAI,uBAAuB,QAAQ,IAAI;AACrD,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,KAAI,KAAK,MAAM,CAAC,EAAG,eAAe,EAAE;AAC3E,SAAO;AACT;AAGA,SAAS,SAAS,KAAqB;AACrC,QAAM,IAAI,eAAe,KAAK,GAAG;AACjC,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,IAAI;AACR,aAAW,MAAM,EAAE,CAAC,EAAG,YAAY,EAAG,KAAI,IAAI,MAAM,GAAG,WAAW,CAAC,IAAI;AACvE,SAAO,IAAI;AACb;AAEA,SAAS,SAAS,MAAe,QAA0B;AACzD,QAAM,IAAI,KAAK,aAAa,GAAG;AAC/B,MAAI,MAAM,aAAa;AACrB,UAAM,KAAK,KAAK,uBAAuB,QAAQ,GAAG;AAClD,WAAO,GAAG,SAAU,GAAG,CAAC,EAAG,eAAe,KAAM;AAAA,EAClD;AACA,QAAM,OAAO,KAAK,uBAAuB,QAAQ,GAAG;AACpD,QAAM,IAAI,KAAK,SAAU,KAAK,CAAC,EAAG,eAAe,KAAM;AACvD,MAAI,MAAM,IAAK,QAAO,OAAO,OAAO,SAAS,GAAG,EAAE,CAAC,KAAK;AACxD,MAAI,MAAM,IAAK,QAAO,MAAM,MAAM,SAAS;AAC3C,SAAO;AACT;AAEA,eAAe,YAAY,KAAmB,MAAc,QAAuC;AACjG,QAAM,MAAM,MAAM,WAAW,KAAK,IAAI;AACtC,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,SAAS,IAAI,uBAAuB,QAAQ,KAAK;AACvD,QAAM,OAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,QAAQ,OAAO,CAAC,EAAG,uBAAuB,QAAQ,GAAG;AAC3D,UAAM,SAAmB,CAAC;AAC1B,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,MAAM,KAAK,aAAa,GAAG;AACjC,YAAM,MAAM,MAAM,SAAS,GAAG,IAAI,OAAO;AACzC,aAAO,OAAO,SAAS,IAAK,QAAO,KAAK,EAAE;AAC1C,aAAO,GAAG,IAAI,SAAS,MAAM,MAAM;AAAA,IACrC;AACA,SAAK,KAAK,MAAM;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,YAAY,MAAiC;AACpD,QAAM,UAAU,KAAK,OAAO,CAAC,GAAG,MAAM,KAAK,IAAI,GAAG,EAAE,MAAM,GAAG,CAAC;AAC9D,QAAM,OAA2B,KAAK,IAAI,CAAC,OAAO,WAAW;AAC3D,UAAM,WAAgC,CAAC;AACvC,aAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,YAAM,QAAQ,MAAM,CAAC,KAAK;AAC1B,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN,GAAI,WAAW,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;AAAA,QACzC,UAAU,QAAQ,CAAC,EAAE,MAAM,QAAQ,MAAM,CAAC,IAAI,CAAC;AAAA,MACjD,CAAC;AAAA,IACH;AACA,WAAO,EAAE,MAAM,YAAY,SAAS;AAAA,EACtC,CAAC;AACD,SAAO,EAAE,MAAM,SAAS,UAAU,KAAK;AACzC;AAEA,eAAsB,kBACpB,MACA,UAA6B,CAAC,GACH;AAC3B,QAAM,MAAM,MAAM,YAAY,MAAM,OAAO;AAC3C,QAAM,SAAS,MAAM,WAAW,GAAG;AACnC,QAAM,SAAS,MAAM,kBAAkB,GAAG;AAE1C,MAAI,WAAW;AACf,MAAI,QAAQ,UAAU,QAAW;AAC/B,UAAM,SACJ,OAAO,QAAQ,UAAU,WACrB,OAAO,QAAQ,KAAK,IACpB,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,KAAK;AACjD,eAAW,SAAS,CAAC,MAAM,IAAI,CAAC;AAAA,EAClC;AAEA,QAAM,WAAgC,CAAC;AACvC,QAAM,SAAS,SAAS,WAAW,KAAK,QAAQ,UAAU;AAC1D,aAAW,SAAS,UAAU;AAC5B,UAAM,OAAO,MAAM,YAAY,KAAK,MAAM,MAAM,MAAM;AACtD,QAAI,CAAC,QAAQ;AACX,eAAS,KAAK,EAAE,MAAM,WAAW,OAAO,GAAG,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,MAAM,KAAK,CAAC,EAAE,CAAC;AAAA,IAC9F;AACA,QAAI,KAAK,SAAS,EAAG,UAAS,KAAK,YAAY,IAAI,CAAC;AAAA,EACtD;AACA,SAAO,EAAE,MAAM,YAAY,SAAS;AACtC;;;ACrIA,SAAS,qBAAqB;AAyC9B,IAAM,aAAa;AAOnB,SAAS,kBAAkB,KAAa,MAAmB,UAA0B;AACnF,MAAI,OAAO,IAAI,QAAQ,gBAAgB,EAAE,EAAE,KAAK;AAChD,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,SAAO,KAAK,MAAM,GAAG,EAAE;AAEvB,MAAI,CAAC,KAAK,IAAI,IAAI,GAAG;AACnB,SAAK,IAAI,IAAI;AACb,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,KAAK,KAAK;AACrB,UAAM,SAAS,OAAO,CAAC;AACvB,UAAM,OAAO,KAAK,MAAM,GAAG,KAAK,OAAO,MAAM;AAC7C,UAAM,YAAY,GAAG,IAAI,GAAG,MAAM;AAClC,QAAI,CAAC,KAAK,IAAI,SAAS,GAAG;AACxB,WAAK,IAAI,SAAS;AAClB,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGA,SAAS,YAAY,OAAkC;AACrD,SAAO,MAAM,SAAS;AAAA,IAAI,CAAC,QACzB,IAAI,SAAS,IAAI,CAAC,SAAS,iBAAiB,KAAK,QAAQ,CAAC;AAAA,EAC5D;AACF;AAGA,SAAS,cAAc,KAAuB,QAA6B;AACzE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAsB,CAAC;AAC7B,MAAI,iBAAgC;AAEpC,QAAM,QAAQ,CAAC,UAAqC;AAClD,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,SAAS,WAAW;AAC3B,yBAAiB,iBAAiB,KAAK,QAAQ;AAAA,MACjD,WAAW,KAAK,SAAS,SAAS;AAChC,cAAM,WAAW,GAAG,MAAM,GAAG,OAAO,SAAS,CAAC;AAC9C,cAAM,OAAO,kBAAkB,kBAAkB,UAAU,MAAM,QAAQ;AACzE,eAAO,KAAK,EAAE,MAAM,MAAM,YAAY,IAAI,EAAE,CAAC;AAC7C,yBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI,QAAQ;AAClB,SAAO;AACT;AAGA,SAAS,aAAa,OAAuB;AAC3C,MAAI,IAAI,QAAQ;AAChB,MAAI,UAAU;AACd,SAAO,IAAI,GAAG;AACZ,UAAM,OAAO,IAAI,KAAK;AACtB,cAAU,OAAO,aAAa,KAAK,GAAG,IAAI;AAC1C,QAAI,KAAK,OAAO,IAAI,KAAK,EAAE;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,OAAe,KAAqB;AACnD,MAAI,WAAW,KAAK,KAAK,GAAG;AAC1B,WAAO,SAAS,GAAG,QAAQ,UAAU,KAAK,CAAC;AAAA,EAC7C;AACA,SAAO,SAAS,GAAG,+CAA+C,UAAU,KAAK,CAAC;AACpF;AAEA,SAAS,aAAa,MAA0B;AAC9C,QAAM,OAAiB,CAAC;AACxB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,QAAQ,KAAK,CAAC;AACpB,UAAM,WAAW,MAAM,IAAI,CAAC,OAAO,MAAM,QAAQ,OAAO,GAAG,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE;AAC9F,SAAK,KAAK,WAAW,IAAI,CAAC,KAAK,QAAQ,QAAQ;AAAA,EACjD;AACA,SACE,GAAG,eAAe,CAAC;AAAA,oBACE,MAAM,cAAc,IAAI,gBAC/B,KAAK,KAAK,EAAE,CAAC;AAG/B;AAEA,SAAS,YAAY,QAA6B;AAChD,QAAM,WAAW,OACd;AAAA,IACC,CAAC,OAAO,MACN,gBAAgB,UAAU,MAAM,IAAI,CAAC,cAAc,IAAI,CAAC,cAAc,IAAI,CAAC;AAAA,EAC/E,EACC,KAAK,EAAE;AACV,SACE,GAAG,eAAe,CAAC;AAAA,mBACC,MAAM,cAAc,IAAI,aACjC,QAAQ;AAGvB;AAGA,SAAS,YAAoB;AAC3B,SACE,GAAG,eAAe,CAAC;AAAA,qBACG,MAAM;AAQhC;AAQA,eAAsB,kBACpB,KACA,UAA6B,CAAC,GACR;AACtB,QAAM,SAAS,QAAQ,mBAAmB;AAC1C,MAAI,SAAS,cAAc,KAAK,MAAM;AAGtC,MAAI,OAAO,WAAW,GAAG;AACvB,aAAS,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC;AAAA,EAC5C;AAEA,QAAM,MAAM,cAAc;AAG1B,SAAO,QAAQ,CAAC,OAAO,MAAM;AAC3B,UAAM,YAAY,sBAAsB,IAAI,CAAC;AAC7C,QAAI,QAAQ,WAAW,aAAa,MAAM,IAAI,GAAG,2BAA2B;AAC5E,QAAI,gBAAgB,mBAAmB;AAAA,MACrC,IAAI,MAAM,IAAI,CAAC;AAAA,MACf,MAAM;AAAA,MACN,QAAQ,mBAAmB,IAAI,CAAC;AAAA,IAClC,CAAC;AAAA,EACH,CAAC;AAGD,MAAI,QAAQ,iBAAiB,UAAU,GAAG,wBAAwB;AAClE,MAAI,gBAAgB,mBAAmB;AAAA,IACrC,IAAI,MAAM,OAAO,SAAS,CAAC;AAAA,IAC3B,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AAGD,MAAI,QAAQ,mBAAmB,YAAY,MAAM,GAAG,0BAA0B;AAC9E,MAAI,gBAAgB,IAAI;AAAA,IACtB,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AAED,MAAI,QAAQ,SAAS,QAAQ,QAAQ;AACnC,QAAI,kBAAkB;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,MAChC,WAAU,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,CAAC;AAAA,EACH;AAEA,SAAO,IAAI,cAAc;AAC3B;AAKA,eAAsB,UAAU,KAAU,SAAmD;AAC3F,SAAO,kBAAkB,cAAc,GAAG,GAAG,OAAO;AACtD;;;AF/NA,eAAsB,UACpB,MACA,SACc;AACd,SAAO,cAAc,MAAM,kBAAkB,MAAM,OAAO,CAAC;AAC7D;","names":[]}
|