@bendyline/squisq-formats 1.4.2 → 2.0.1
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-2KPARF2P.js +99 -0
- package/dist/chunk-2KPARF2P.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-ABVI556T.js} +154 -39
- package/dist/chunk-ABVI556T.js.map +1 -0
- package/dist/{chunk-WWBIKRNP.js → chunk-CRAVSMPZ.js} +13 -11
- package/dist/chunk-CRAVSMPZ.js.map +1 -0
- package/dist/chunk-FE6OJV6O.js +560 -0
- package/dist/chunk-FE6OJV6O.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-RTS5XBZ7.js → chunk-HTW2M27H.js} +341 -100
- package/dist/chunk-HTW2M27H.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-QRVN6A6E.js +127 -0
- package/dist/chunk-QRVN6A6E.js.map +1 -0
- package/dist/chunk-U32AG3G3.js +195 -0
- package/dist/chunk-U32AG3G3.js.map +1 -0
- package/dist/{chunk-EHLGMSTE.js → chunk-VJJM2SSH.js} +10 -8
- package/dist/chunk-VJJM2SSH.js.map +1 -0
- package/dist/chunk-VSYHZECT.js +778 -0
- package/dist/chunk-VSYHZECT.js.map +1 -0
- package/dist/{chunk-SHLPSADL.js → chunk-WC7WULGV.js} +81 -189
- package/dist/chunk-WC7WULGV.js.map +1 -0
- package/dist/chunk-XKUMNGBW.js +131 -0
- package/dist/chunk-XKUMNGBW.js.map +1 -0
- package/dist/chunk-YRT7GQ5Y.js +28 -0
- package/dist/chunk-YRT7GQ5Y.js.map +1 -0
- package/dist/container/index.d.ts +13 -3
- package/dist/container/index.js +23 -20
- 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-D2NkylDT.d.ts +69 -0
- package/dist/extract-H6RXJMHP.js +15 -0
- package/dist/extract-H6RXJMHP.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-K8mfc0fz.d.ts +136 -0
- package/dist/index.d.ts +14 -4
- package/dist/index.js +33 -12
- 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-BHrgZ5FS.d.ts +150 -0
- package/dist/layouts-QVPK3ZCU.js +12 -0
- package/dist/layouts-QVPK3ZCU.js.map +1 -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-B9L8Ucbj.d.ts +140 -0
- package/dist/registry/index.d.ts +60 -8
- package/dist/registry/index.js +9 -4
- package/dist/themeReader-DJKErl_j.d.ts +56 -0
- package/dist/xlsx/index.d.ts +6 -66
- package/dist/xlsx/index.js +4 -2
- package/dist/zipLimits-BOKCB7qk.d.ts +31 -0
- package/package.json +8 -2
- package/src/__tests__/container.test.ts +41 -0
- package/src/__tests__/convert.test.ts +310 -1
- 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 +76 -2
- package/src/__tests__/ooxmlCancellation.test.ts +113 -0
- 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 +37 -32
- 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 +23 -0
- package/src/infer/extract.ts +127 -0
- package/src/infer/index.ts +199 -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 +129 -29
- 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 +1222 -0
- package/src/registry/convert.ts +168 -53
- package/src/registry/defaultFormats.ts +135 -45
- package/src/registry/index.ts +6 -1
- package/src/registry/types.ts +69 -6
- package/src/shared/boundedZipArchive.ts +383 -0
- package/src/shared/fidelity.ts +130 -0
- package/src/shared/zipEntryCount.ts +151 -0
- package/src/shared/zipLimits.ts +296 -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,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConversionError
|
|
3
|
+
} from "./chunk-ODL3SSPT.js";
|
|
4
|
+
import {
|
|
5
|
+
extractDocxTheme,
|
|
6
|
+
extractPptxTheme,
|
|
7
|
+
extractXlsxTheme
|
|
8
|
+
} from "./chunk-2KPARF2P.js";
|
|
9
|
+
import {
|
|
10
|
+
colorHintsFromExtraction,
|
|
11
|
+
compileExtractedTheme
|
|
12
|
+
} from "./chunk-NKAJPJ4G.js";
|
|
13
|
+
import {
|
|
14
|
+
openPackage,
|
|
15
|
+
throwIfOoxmlAborted
|
|
16
|
+
} from "./chunk-WC7WULGV.js";
|
|
17
|
+
|
|
18
|
+
// src/infer/index.ts
|
|
19
|
+
async function looksLikePdf(data, signal) {
|
|
20
|
+
throwIfOoxmlAborted(signal);
|
|
21
|
+
let head = null;
|
|
22
|
+
const blob = data;
|
|
23
|
+
if (typeof blob.size === "number" && typeof blob.slice === "function" && typeof blob.arrayBuffer === "function") {
|
|
24
|
+
head = new Uint8Array(await waitForAbortable(blob.slice(0, 5).arrayBuffer(), signal));
|
|
25
|
+
} else if (typeof data.byteLength === "number") {
|
|
26
|
+
const buf = data;
|
|
27
|
+
head = new Uint8Array(buf, 0, Math.min(5, buf.byteLength));
|
|
28
|
+
}
|
|
29
|
+
throwIfOoxmlAborted(signal);
|
|
30
|
+
if (!head || head.length < 4) return false;
|
|
31
|
+
return head[0] === 37 && head[1] === 80 && head[2] === 68 && head[3] === 70;
|
|
32
|
+
}
|
|
33
|
+
function waitForAbortable(work, signal) {
|
|
34
|
+
throwIfOoxmlAborted(signal);
|
|
35
|
+
if (!signal) return work;
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
let settled = false;
|
|
38
|
+
const finish = (complete) => {
|
|
39
|
+
if (settled) return;
|
|
40
|
+
settled = true;
|
|
41
|
+
signal.removeEventListener("abort", handleAbort);
|
|
42
|
+
complete();
|
|
43
|
+
};
|
|
44
|
+
const handleAbort = () => finish(() => reject(signal.reason ?? new Error("OOXML operation was cancelled")));
|
|
45
|
+
signal.addEventListener("abort", handleAbort, { once: true });
|
|
46
|
+
if (signal.aborted) handleAbort();
|
|
47
|
+
work.then(
|
|
48
|
+
(value) => finish(() => resolve(value)),
|
|
49
|
+
(error) => finish(() => reject(error))
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function sniffOoxmlFormat(pkg) {
|
|
54
|
+
const values = [...pkg.contentTypes.overrides.values(), ...pkg.contentTypes.defaults.values()];
|
|
55
|
+
for (const ct of values) {
|
|
56
|
+
if (ct.includes("wordprocessingml")) return "docx";
|
|
57
|
+
if (ct.includes("presentationml")) return "pptx";
|
|
58
|
+
if (ct.includes("spreadsheetml")) return "xlsx";
|
|
59
|
+
}
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
var EXTRACTORS = {
|
|
63
|
+
docx: extractDocxTheme,
|
|
64
|
+
pptx: extractPptxTheme,
|
|
65
|
+
xlsx: extractXlsxTheme
|
|
66
|
+
};
|
|
67
|
+
async function inferThemeFromFile(data, options = {}) {
|
|
68
|
+
throwIfOoxmlAborted(options.signal);
|
|
69
|
+
if (await looksLikePdf(data, options.signal)) {
|
|
70
|
+
throw new ConversionError(
|
|
71
|
+
"unsupported-input",
|
|
72
|
+
"PDF theme inference is not supported \u2014 PDF files carry no theme color/font tables.",
|
|
73
|
+
{ format: "pdf" }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
let pkg;
|
|
77
|
+
try {
|
|
78
|
+
pkg = await openPackage(data, options);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (options.signal?.aborted) throw options.signal.reason ?? err;
|
|
81
|
+
throw new ConversionError(
|
|
82
|
+
"invalid-input",
|
|
83
|
+
"Could not read this file as a Word, PowerPoint, or Excel document.",
|
|
84
|
+
{ cause: err }
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const format = options.format ?? sniffOoxmlFormat(pkg);
|
|
88
|
+
throwIfOoxmlAborted(options.signal);
|
|
89
|
+
if (!format) {
|
|
90
|
+
throw new ConversionError(
|
|
91
|
+
"invalid-input",
|
|
92
|
+
"Not a Word, PowerPoint, or Excel document \u2014 no theme to infer."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const extraction = await EXTRACTORS[format](pkg);
|
|
96
|
+
throwIfOoxmlAborted(options.signal);
|
|
97
|
+
if (!extraction) {
|
|
98
|
+
throw new ConversionError("invalid-input", "No theme part found in this file.", { format });
|
|
99
|
+
}
|
|
100
|
+
const { theme, warnings: mapWarnings } = compileExtractedTheme(extraction, {
|
|
101
|
+
nameHint: options.nameHint
|
|
102
|
+
});
|
|
103
|
+
throwIfOoxmlAborted(options.signal);
|
|
104
|
+
const warnings = [...extraction.warnings, ...mapWarnings];
|
|
105
|
+
let layouts;
|
|
106
|
+
if (options.inferLayouts) {
|
|
107
|
+
if (format !== "pptx") {
|
|
108
|
+
warnings.push("Layout inference is only available for PowerPoint files; skipped.");
|
|
109
|
+
} else {
|
|
110
|
+
const { analyzePptxLayouts } = await import("./layouts-QVPK3ZCU.js");
|
|
111
|
+
const analysis = await analyzePptxLayouts(pkg, {
|
|
112
|
+
colors: colorHintsFromExtraction(extraction),
|
|
113
|
+
signal: options.signal
|
|
114
|
+
});
|
|
115
|
+
warnings.push(...analysis.warnings);
|
|
116
|
+
const defs = analysis.layouts.map((l) => l.verdict.kind === "custom" ? l.verdict.def : null).filter((d) => d !== null);
|
|
117
|
+
if (defs.length > 0) layouts = defs;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
throwIfOoxmlAborted(options.signal);
|
|
121
|
+
return { theme, extraction, ...layouts ? { layouts } : {}, warnings };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
inferThemeFromFile
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=chunk-QRVN6A6E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/infer/index.ts"],"sourcesContent":["/**\n * @bendyline/squisq-formats Infer Module\n *\n * \"Infer theme and layouts from a file import\": read the OOXML theme\n * (colors + fonts) out of a DOCX/PPTX/XLSX file and compile it into a\n * Squisq `Theme`; for PPTX, optionally derive custom layout templates\n * from the deck's slide layouts/masters.\n *\n * Consumers:\n * - the editor's theme customizer surfaces (upload / drag-drop a file)\n * - the PPTX importer (theme + layouts ride along as frontmatter)\n *\n * PDF is deliberately unsupported: PDF files carry no theme tables, so\n * inference would produce a mostly-default theme. `inferThemeFromFile`\n * rejects PDF bytes with a clear `ConversionError`.\n */\n\nimport type { CustomTemplateDefinition, Theme } from '@bendyline/squisq/schemas';\nimport { openPackage, throwIfOoxmlAborted } from '../ooxml/reader.js';\nimport type { OoxmlOpenOptions } from '../ooxml/reader.js';\nimport type { OoxmlPackage } from '../ooxml/types.js';\nimport { ConversionError } from '../registry/errors.js';\nimport type { ExtractedFileTheme, InferSourceFormat } from './types.js';\nimport { extractDocxTheme, extractPptxTheme, extractXlsxTheme } from './extract.js';\nimport { colorHintsFromExtraction, compileExtractedTheme } from './mapTheme.js';\n\nexport type { ExtractedFileTheme, InferSourceFormat, SchemeSlot } from './types.js';\nexport { extractDocxTheme, extractPptxTheme, extractXlsxTheme } from './extract.js';\nexport type { CompileExtractedOptions, MappedThemePartial } from './mapTheme.js';\nexport {\n colorHintsFromExtraction,\n compileExtractedTheme,\n extractedThemeToPartial,\n} from './mapTheme.js';\n\nexport interface InferThemeOptions extends OoxmlOpenOptions {\n /** Skip sniffing when the caller already knows the format (e.g. from the extension). */\n format?: InferSourceFormat;\n /** PPTX only: also derive custom layout templates from slide layouts/masters. */\n inferLayouts?: boolean;\n /** Preferred display name for the compiled theme (e.g. the file's basename). */\n nameHint?: string;\n}\n\nexport interface InferredFileTheme {\n /** Compiled, validated Squisq theme (`custom-<slug>` id, seeds recorded). */\n theme: Theme;\n /** Raw extraction, for callers mapping into their own editing model. */\n extraction: ExtractedFileTheme;\n /** Present only for PPTX with `inferLayouts` and ≥1 non-redundant layout. */\n layouts?: CustomTemplateDefinition[];\n /** Non-fatal notes: sysClr fallbacks, dropped accents, skipped layouts, … */\n warnings: string[];\n}\n\nasync function looksLikePdf(data: ArrayBuffer | Blob, signal?: AbortSignal): Promise<boolean> {\n throwIfOoxmlAborted(signal);\n // Duck-typed (not instanceof): buffers routinely cross realms in test\n // environments and web workers. Blobs carry `size`; ArrayBuffers carry\n // `byteLength`.\n let head: Uint8Array | null = null;\n const blob = data as Blob;\n if (\n typeof blob.size === 'number' &&\n typeof blob.slice === 'function' &&\n typeof blob.arrayBuffer === 'function'\n ) {\n head = new Uint8Array(await waitForAbortable(blob.slice(0, 5).arrayBuffer(), signal));\n } else if (typeof (data as ArrayBuffer).byteLength === 'number') {\n const buf = data as ArrayBuffer;\n head = new Uint8Array(buf, 0, Math.min(5, buf.byteLength));\n }\n throwIfOoxmlAborted(signal);\n if (!head || head.length < 4) return false;\n // '%PDF'\n return head[0] === 0x25 && head[1] === 0x50 && head[2] === 0x44 && head[3] === 0x46;\n}\n\nfunction waitForAbortable<T>(work: Promise<T>, signal?: AbortSignal): Promise<T> {\n throwIfOoxmlAborted(signal);\n if (!signal) return work;\n return new Promise<T>((resolve, reject) => {\n let settled = false;\n const finish = (complete: () => void): void => {\n if (settled) return;\n settled = true;\n signal.removeEventListener('abort', handleAbort);\n complete();\n };\n const handleAbort = (): void =>\n finish(() => reject(signal.reason ?? new Error('OOXML operation was cancelled')));\n signal.addEventListener('abort', handleAbort, { once: true });\n if (signal.aborted) handleAbort();\n work.then(\n (value) => finish(() => resolve(value)),\n (error: unknown) => finish(() => reject(error)),\n );\n });\n}\n\n/**\n * Classify an opened package from its already-parsed content types.\n * (Deliberately not the registry's byte sniffer — that re-parses the zip\n * and carries `dbk` semantics this path doesn't want.)\n */\nfunction sniffOoxmlFormat(pkg: OoxmlPackage): InferSourceFormat | undefined {\n const values = [...pkg.contentTypes.overrides.values(), ...pkg.contentTypes.defaults.values()];\n for (const ct of values) {\n if (ct.includes('wordprocessingml')) return 'docx';\n if (ct.includes('presentationml')) return 'pptx';\n if (ct.includes('spreadsheetml')) return 'xlsx';\n }\n return undefined;\n}\n\nconst EXTRACTORS: Record<\n InferSourceFormat,\n (pkg: OoxmlPackage) => Promise<ExtractedFileTheme | null>\n> = {\n docx: extractDocxTheme,\n pptx: extractPptxTheme,\n xlsx: extractXlsxTheme,\n};\n\n/**\n * Infer a Squisq theme (and, for PPTX, optionally custom layout templates)\n * from an office file's bytes.\n *\n * Throws `ConversionError`:\n * - `unsupported-input` for PDF bytes (no theme tables to read)\n * - `invalid-input` for non-OOXML data or files with no theme part\n */\nexport async function inferThemeFromFile(\n data: ArrayBuffer | Blob,\n options: InferThemeOptions = {},\n): Promise<InferredFileTheme> {\n throwIfOoxmlAborted(options.signal);\n if (await looksLikePdf(data, options.signal)) {\n throw new ConversionError(\n 'unsupported-input',\n 'PDF theme inference is not supported — PDF files carry no theme color/font tables.',\n { format: 'pdf' },\n );\n }\n\n let pkg: OoxmlPackage;\n try {\n pkg = await openPackage(data, options);\n } catch (err: unknown) {\n if (options.signal?.aborted) throw options.signal.reason ?? err;\n throw new ConversionError(\n 'invalid-input',\n 'Could not read this file as a Word, PowerPoint, or Excel document.',\n { cause: err },\n );\n }\n\n const format = options.format ?? sniffOoxmlFormat(pkg);\n throwIfOoxmlAborted(options.signal);\n if (!format) {\n throw new ConversionError(\n 'invalid-input',\n 'Not a Word, PowerPoint, or Excel document — no theme to infer.',\n );\n }\n\n const extraction = await EXTRACTORS[format](pkg);\n throwIfOoxmlAborted(options.signal);\n if (!extraction) {\n throw new ConversionError('invalid-input', 'No theme part found in this file.', { format });\n }\n\n const { theme, warnings: mapWarnings } = compileExtractedTheme(extraction, {\n nameHint: options.nameHint,\n });\n throwIfOoxmlAborted(options.signal);\n const warnings = [...extraction.warnings, ...mapWarnings];\n\n let layouts: CustomTemplateDefinition[] | undefined;\n if (options.inferLayouts) {\n if (format !== 'pptx') {\n warnings.push('Layout inference is only available for PowerPoint files; skipped.');\n } else {\n const { analyzePptxLayouts } = await import('../pptx/layouts.js');\n const analysis = await analyzePptxLayouts(pkg, {\n colors: colorHintsFromExtraction(extraction),\n signal: options.signal,\n });\n warnings.push(...analysis.warnings);\n const defs = analysis.layouts\n .map((l) => (l.verdict.kind === 'custom' ? l.verdict.def : null))\n .filter((d): d is CustomTemplateDefinition => d !== null);\n if (defs.length > 0) layouts = defs;\n }\n }\n\n throwIfOoxmlAborted(options.signal);\n return { theme, extraction, ...(layouts ? { layouts } : {}), warnings };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAuDA,eAAe,aAAa,MAA0B,QAAwC;AAC5F,sBAAoB,MAAM;AAI1B,MAAI,OAA0B;AAC9B,QAAM,OAAO;AACb,MACE,OAAO,KAAK,SAAS,YACrB,OAAO,KAAK,UAAU,cACtB,OAAO,KAAK,gBAAgB,YAC5B;AACA,WAAO,IAAI,WAAW,MAAM,iBAAiB,KAAK,MAAM,GAAG,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;AAAA,EACtF,WAAW,OAAQ,KAAqB,eAAe,UAAU;AAC/D,UAAM,MAAM;AACZ,WAAO,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI,GAAG,IAAI,UAAU,CAAC;AAAA,EAC3D;AACA,sBAAoB,MAAM;AAC1B,MAAI,CAAC,QAAQ,KAAK,SAAS,EAAG,QAAO;AAErC,SAAO,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM;AACjF;AAEA,SAAS,iBAAoB,MAAkB,QAAkC;AAC/E,sBAAoB,MAAM;AAC1B,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,QAAI,UAAU;AACd,UAAM,SAAS,CAAC,aAA+B;AAC7C,UAAI,QAAS;AACb,gBAAU;AACV,aAAO,oBAAoB,SAAS,WAAW;AAC/C,eAAS;AAAA,IACX;AACA,UAAM,cAAc,MAClB,OAAO,MAAM,OAAO,OAAO,UAAU,IAAI,MAAM,+BAA+B,CAAC,CAAC;AAClF,WAAO,iBAAiB,SAAS,aAAa,EAAE,MAAM,KAAK,CAAC;AAC5D,QAAI,OAAO,QAAS,aAAY;AAChC,SAAK;AAAA,MACH,CAAC,UAAU,OAAO,MAAM,QAAQ,KAAK,CAAC;AAAA,MACtC,CAAC,UAAmB,OAAO,MAAM,OAAO,KAAK,CAAC;AAAA,IAChD;AAAA,EACF,CAAC;AACH;AAOA,SAAS,iBAAiB,KAAkD;AAC1E,QAAM,SAAS,CAAC,GAAG,IAAI,aAAa,UAAU,OAAO,GAAG,GAAG,IAAI,aAAa,SAAS,OAAO,CAAC;AAC7F,aAAW,MAAM,QAAQ;AACvB,QAAI,GAAG,SAAS,kBAAkB,EAAG,QAAO;AAC5C,QAAI,GAAG,SAAS,gBAAgB,EAAG,QAAO;AAC1C,QAAI,GAAG,SAAS,eAAe,EAAG,QAAO;AAAA,EAC3C;AACA,SAAO;AACT;AAEA,IAAM,aAGF;AAAA,EACF,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAUA,eAAsB,mBACpB,MACA,UAA6B,CAAC,GACF;AAC5B,sBAAoB,QAAQ,MAAM;AAClC,MAAI,MAAM,aAAa,MAAM,QAAQ,MAAM,GAAG;AAC5C,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,QAAQ,MAAM;AAAA,IAClB;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,YAAY,MAAM,OAAO;AAAA,EACvC,SAAS,KAAc;AACrB,QAAI,QAAQ,QAAQ,QAAS,OAAM,QAAQ,OAAO,UAAU;AAC5D,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,OAAO,IAAI;AAAA,IACf;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,UAAU,iBAAiB,GAAG;AACrD,sBAAoB,QAAQ,MAAM;AAClC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,MAAM,WAAW,MAAM,EAAE,GAAG;AAC/C,sBAAoB,QAAQ,MAAM;AAClC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,gBAAgB,iBAAiB,qCAAqC,EAAE,OAAO,CAAC;AAAA,EAC5F;AAEA,QAAM,EAAE,OAAO,UAAU,YAAY,IAAI,sBAAsB,YAAY;AAAA,IACzE,UAAU,QAAQ;AAAA,EACpB,CAAC;AACD,sBAAoB,QAAQ,MAAM;AAClC,QAAM,WAAW,CAAC,GAAG,WAAW,UAAU,GAAG,WAAW;AAExD,MAAI;AACJ,MAAI,QAAQ,cAAc;AACxB,QAAI,WAAW,QAAQ;AACrB,eAAS,KAAK,mEAAmE;AAAA,IACnF,OAAO;AACL,YAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,uBAAoB;AAChE,YAAM,WAAW,MAAM,mBAAmB,KAAK;AAAA,QAC7C,QAAQ,yBAAyB,UAAU;AAAA,QAC3C,QAAQ,QAAQ;AAAA,MAClB,CAAC;AACD,eAAS,KAAK,GAAG,SAAS,QAAQ;AAClC,YAAM,OAAO,SAAS,QACnB,IAAI,CAAC,MAAO,EAAE,QAAQ,SAAS,WAAW,EAAE,QAAQ,MAAM,IAAK,EAC/D,OAAO,CAAC,MAAqC,MAAM,IAAI;AAC1D,UAAI,KAAK,SAAS,EAAG,WAAU;AAAA,IACjC;AAAA,EACF;AAEA,sBAAoB,QAAQ,MAAM;AAClC,SAAO,EAAE,OAAO,YAAY,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,GAAI,SAAS;AACxE;","names":[]}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeXml,
|
|
3
|
+
xmlDeclaration
|
|
4
|
+
} from "./chunk-U4MRIFKL.js";
|
|
5
|
+
import {
|
|
6
|
+
CONTENT_TYPE_CORE_PROPERTIES,
|
|
7
|
+
CONTENT_TYPE_RELATIONSHIPS,
|
|
8
|
+
NS_CONTENT_TYPES,
|
|
9
|
+
NS_CORE_PROPERTIES,
|
|
10
|
+
NS_DC,
|
|
11
|
+
NS_DCTERMS,
|
|
12
|
+
NS_RELATIONSHIPS,
|
|
13
|
+
NS_XSI,
|
|
14
|
+
REL_CORE_PROPERTIES
|
|
15
|
+
} from "./chunk-WC7WULGV.js";
|
|
16
|
+
import {
|
|
17
|
+
assertSafeZipPath
|
|
18
|
+
} from "./chunk-FE6OJV6O.js";
|
|
19
|
+
|
|
20
|
+
// src/ooxml/writer.ts
|
|
21
|
+
import JSZip from "jszip";
|
|
22
|
+
function createPackage() {
|
|
23
|
+
const parts = [];
|
|
24
|
+
const relationships = [];
|
|
25
|
+
let coreProps;
|
|
26
|
+
return {
|
|
27
|
+
addPart(path, content, contentType) {
|
|
28
|
+
assertSafeZipPath(path);
|
|
29
|
+
parts.push({ path, content, contentType });
|
|
30
|
+
},
|
|
31
|
+
addBinaryPart(path, data, contentType) {
|
|
32
|
+
assertSafeZipPath(path);
|
|
33
|
+
parts.push({ path, binaryContent: data, contentType });
|
|
34
|
+
},
|
|
35
|
+
addRelationship(sourcePart, rel) {
|
|
36
|
+
if (sourcePart) assertSafeZipPath(sourcePart);
|
|
37
|
+
relationships.push({ sourcePart, relationship: rel });
|
|
38
|
+
},
|
|
39
|
+
setCoreProperties(props) {
|
|
40
|
+
coreProps = props;
|
|
41
|
+
},
|
|
42
|
+
async toBlob() {
|
|
43
|
+
const zip = assemble(parts, relationships, coreProps);
|
|
44
|
+
return zip.generateAsync({ type: "blob" });
|
|
45
|
+
},
|
|
46
|
+
async toArrayBuffer() {
|
|
47
|
+
const zip = assemble(parts, relationships, coreProps);
|
|
48
|
+
return zip.generateAsync({ type: "arraybuffer" });
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function assemble(parts, relationships, coreProps) {
|
|
53
|
+
const zip = new JSZip();
|
|
54
|
+
const assembledRelationships = relationships.slice();
|
|
55
|
+
for (const part of parts) {
|
|
56
|
+
if (part.content !== void 0) {
|
|
57
|
+
zip.file(part.path, part.content);
|
|
58
|
+
} else if (part.binaryContent !== void 0) {
|
|
59
|
+
zip.file(part.path, part.binaryContent);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (coreProps) {
|
|
63
|
+
const coreXml = buildCorePropertiesXml(coreProps);
|
|
64
|
+
zip.file("docProps/core.xml", coreXml);
|
|
65
|
+
if (!assembledRelationships.some(
|
|
66
|
+
({ sourcePart, relationship }) => sourcePart === "" && relationship.type === REL_CORE_PROPERTIES
|
|
67
|
+
)) {
|
|
68
|
+
const ids = new Set(assembledRelationships.map(({ relationship }) => relationship.id));
|
|
69
|
+
let nextId = 100;
|
|
70
|
+
while (ids.has(`rId${nextId}`)) nextId++;
|
|
71
|
+
assembledRelationships.push({
|
|
72
|
+
sourcePart: "",
|
|
73
|
+
relationship: {
|
|
74
|
+
id: `rId${nextId}`,
|
|
75
|
+
type: REL_CORE_PROPERTIES,
|
|
76
|
+
target: "docProps/core.xml"
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
zip.file("[Content_Types].xml", buildContentTypesXml(parts, coreProps));
|
|
82
|
+
const relsBySource = groupRelationshipsBySource(assembledRelationships);
|
|
83
|
+
for (const [sourcePart, rels] of relsBySource) {
|
|
84
|
+
const relsPath = sourcePart === "" ? "_rels/.rels" : buildRelsPath(sourcePart);
|
|
85
|
+
zip.file(relsPath, buildRelationshipsXml(rels));
|
|
86
|
+
}
|
|
87
|
+
return zip;
|
|
88
|
+
}
|
|
89
|
+
function buildContentTypesXml(parts, coreProps) {
|
|
90
|
+
const lines = [xmlDeclaration()];
|
|
91
|
+
lines.push(`<Types xmlns="${NS_CONTENT_TYPES}">`);
|
|
92
|
+
const extensionTypes = /* @__PURE__ */ new Map();
|
|
93
|
+
extensionTypes.set("rels", CONTENT_TYPE_RELATIONSHIPS);
|
|
94
|
+
extensionTypes.set("xml", "application/xml");
|
|
95
|
+
for (const part of parts) {
|
|
96
|
+
if (part.binaryContent !== void 0) {
|
|
97
|
+
const ext = getExtension(part.path);
|
|
98
|
+
if (ext && !extensionTypes.has(ext)) {
|
|
99
|
+
extensionTypes.set(ext, part.contentType);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const [ext, ct] of extensionTypes) {
|
|
104
|
+
lines.push(` <Default Extension="${escapeXml(ext)}" ContentType="${escapeXml(ct)}"/>`);
|
|
105
|
+
}
|
|
106
|
+
for (const part of parts) {
|
|
107
|
+
if (part.content !== void 0) {
|
|
108
|
+
lines.push(
|
|
109
|
+
` <Override PartName="/${escapeXml(part.path)}" ContentType="${escapeXml(part.contentType)}"/>`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (coreProps) {
|
|
114
|
+
lines.push(
|
|
115
|
+
` <Override PartName="/docProps/core.xml" ContentType="${CONTENT_TYPE_CORE_PROPERTIES}"/>`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
lines.push("</Types>");
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
function buildRelationshipsXml(rels) {
|
|
122
|
+
const lines = [xmlDeclaration()];
|
|
123
|
+
lines.push(`<Relationships xmlns="${NS_RELATIONSHIPS}">`);
|
|
124
|
+
for (const rel of rels) {
|
|
125
|
+
const attrs = [
|
|
126
|
+
`Id="${escapeXml(rel.id)}"`,
|
|
127
|
+
`Type="${escapeXml(rel.type)}"`,
|
|
128
|
+
`Target="${escapeXml(rel.target)}"`
|
|
129
|
+
];
|
|
130
|
+
if (rel.targetMode) {
|
|
131
|
+
attrs.push(`TargetMode="${escapeXml(rel.targetMode)}"`);
|
|
132
|
+
}
|
|
133
|
+
lines.push(` <Relationship ${attrs.join(" ")}/>`);
|
|
134
|
+
}
|
|
135
|
+
lines.push("</Relationships>");
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
function buildCorePropertiesXml(props) {
|
|
139
|
+
const lines = [xmlDeclaration()];
|
|
140
|
+
lines.push(
|
|
141
|
+
`<cp:coreProperties xmlns:cp="${NS_CORE_PROPERTIES}" xmlns:dc="${NS_DC}" xmlns:dcterms="${NS_DCTERMS}" xmlns:xsi="${NS_XSI}">`
|
|
142
|
+
);
|
|
143
|
+
if (props.title) lines.push(` <dc:title>${escapeXml(props.title)}</dc:title>`);
|
|
144
|
+
if (props.subject) lines.push(` <dc:subject>${escapeXml(props.subject)}</dc:subject>`);
|
|
145
|
+
if (props.creator) lines.push(` <dc:creator>${escapeXml(props.creator)}</dc:creator>`);
|
|
146
|
+
if (props.description)
|
|
147
|
+
lines.push(` <dc:description>${escapeXml(props.description)}</dc:description>`);
|
|
148
|
+
if (props.keywords) lines.push(` <cp:keywords>${escapeXml(props.keywords)}</cp:keywords>`);
|
|
149
|
+
if (props.lastModifiedBy)
|
|
150
|
+
lines.push(` <cp:lastModifiedBy>${escapeXml(props.lastModifiedBy)}</cp:lastModifiedBy>`);
|
|
151
|
+
if (props.revision) lines.push(` <cp:revision>${escapeXml(props.revision)}</cp:revision>`);
|
|
152
|
+
if (props.created) {
|
|
153
|
+
lines.push(
|
|
154
|
+
` <dcterms:created xsi:type="dcterms:W3CDTF">${escapeXml(props.created)}</dcterms:created>`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (props.modified) {
|
|
158
|
+
lines.push(
|
|
159
|
+
` <dcterms:modified xsi:type="dcterms:W3CDTF">${escapeXml(props.modified)}</dcterms:modified>`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
lines.push("</cp:coreProperties>");
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|
|
165
|
+
function getExtension(path) {
|
|
166
|
+
const dot = path.lastIndexOf(".");
|
|
167
|
+
if (dot === -1) return void 0;
|
|
168
|
+
return path.substring(dot + 1).toLowerCase();
|
|
169
|
+
}
|
|
170
|
+
function buildRelsPath(partPath) {
|
|
171
|
+
const lastSlash = partPath.lastIndexOf("/");
|
|
172
|
+
if (lastSlash === -1) {
|
|
173
|
+
return `_rels/${partPath}.rels`;
|
|
174
|
+
}
|
|
175
|
+
const dir = partPath.substring(0, lastSlash);
|
|
176
|
+
const file = partPath.substring(lastSlash + 1);
|
|
177
|
+
return `${dir}/_rels/${file}.rels`;
|
|
178
|
+
}
|
|
179
|
+
function groupRelationshipsBySource(pending) {
|
|
180
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const { sourcePart, relationship } of pending) {
|
|
182
|
+
let list = grouped.get(sourcePart);
|
|
183
|
+
if (!list) {
|
|
184
|
+
list = [];
|
|
185
|
+
grouped.set(sourcePart, list);
|
|
186
|
+
}
|
|
187
|
+
list.push(relationship);
|
|
188
|
+
}
|
|
189
|
+
return grouped;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export {
|
|
193
|
+
createPackage
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=chunk-U32AG3G3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ooxml/writer.ts"],"sourcesContent":["/**\n * OOXML Package Writer\n *\n * Builds OOXML archives (.docx, .pptx, .xlsx) from parts.\n * Handles automatic generation of [Content_Types].xml and\n * _rels/*.rels files from the registered parts and relationships.\n */\n\nimport JSZip from 'jszip';\nimport type { CoreProperties, PackagePart, PendingRelationship, Relationship } from './types.js';\nimport {\n NS_CONTENT_TYPES,\n NS_RELATIONSHIPS,\n NS_CORE_PROPERTIES,\n NS_DC,\n NS_DCTERMS,\n NS_XSI,\n CONTENT_TYPE_RELATIONSHIPS,\n CONTENT_TYPE_CORE_PROPERTIES,\n REL_CORE_PROPERTIES,\n} from './namespaces.js';\nimport { xmlDeclaration, escapeXml } from './xmlUtils.js';\nimport { assertSafeZipPath } from '../shared/zipSafety.js';\n\n// ============================================\n// Package Builder\n// ============================================\n\n/**\n * Mutable builder for constructing an OOXML package.\n *\n * Add parts, relationships, and core properties, then call `toBlob()`\n * or `toArrayBuffer()` to produce the final ZIP archive.\n *\n * @example\n * ```ts\n * const builder = createPackage();\n * builder.addPart('word/document.xml', documentXml, CONTENT_TYPE_DOCX_DOCUMENT);\n * builder.addRelationship('', {\n * id: 'rId1',\n * type: REL_OFFICE_DOCUMENT,\n * target: 'word/document.xml',\n * });\n * const blob = await builder.toBlob();\n * ```\n */\nexport interface OoxmlPackageBuilder {\n /**\n * Add an XML or text part to the package.\n *\n * @param path - Path within the archive (e.g., \"word/document.xml\")\n * @param content - XML string content\n * @param contentType - MIME content type for [Content_Types].xml\n */\n addPart(path: string, content: string, contentType: string): void;\n\n /**\n * Add a binary part to the package (e.g., an image).\n *\n * @param path - Path within the archive (e.g., \"word/media/image1.png\")\n * @param data - Binary content\n * @param contentType - MIME content type (e.g., \"image/png\")\n */\n addBinaryPart(path: string, data: ArrayBuffer | Uint8Array, contentType: string): void;\n\n /**\n * Register a relationship.\n *\n * @param sourcePart - The part this relationship belongs to (e.g., \"word/document.xml\").\n * Use \"\" (empty string) for root-level relationships (_rels/.rels).\n * @param rel - The relationship entry\n */\n addRelationship(sourcePart: string, rel: Relationship): void;\n\n /**\n * Set core document properties (docProps/core.xml).\n * Calling this multiple times overwrites previous values.\n */\n setCoreProperties(props: CoreProperties): void;\n\n /**\n * Assemble the final OOXML package as a Blob.\n * Generates [Content_Types].xml and all _rels/*.rels files automatically.\n */\n toBlob(): Promise<Blob>;\n\n /**\n * Assemble the final OOXML package as an ArrayBuffer.\n */\n toArrayBuffer(): Promise<ArrayBuffer>;\n}\n\n/**\n * Create a new OOXML package builder.\n */\nexport function createPackage(): OoxmlPackageBuilder {\n const parts: PackagePart[] = [];\n const relationships: PendingRelationship[] = [];\n let coreProps: CoreProperties | undefined;\n\n return {\n addPart(path, content, contentType) {\n assertSafeZipPath(path);\n parts.push({ path, content, contentType });\n },\n\n addBinaryPart(path, data, contentType) {\n assertSafeZipPath(path);\n parts.push({ path, binaryContent: data, contentType });\n },\n\n addRelationship(sourcePart, rel) {\n if (sourcePart) assertSafeZipPath(sourcePart);\n relationships.push({ sourcePart, relationship: rel });\n },\n\n setCoreProperties(props) {\n coreProps = props;\n },\n\n async toBlob() {\n const zip = assemble(parts, relationships, coreProps);\n return zip.generateAsync({ type: 'blob' });\n },\n\n async toArrayBuffer() {\n const zip = assemble(parts, relationships, coreProps);\n return zip.generateAsync({ type: 'arraybuffer' });\n },\n };\n}\n\n// ============================================\n// Assembly\n// ============================================\n\n/**\n * Assemble a JSZip archive from parts, relationships, and core properties.\n */\nfunction assemble(\n parts: PackagePart[],\n relationships: PendingRelationship[],\n coreProps?: CoreProperties,\n): JSZip {\n const zip = new JSZip();\n // Assembly must be idempotent: callers may request both Blob and\n // ArrayBuffer representations from one builder. Never append generated\n // relationships to the builder-owned array.\n const assembledRelationships = relationships.slice();\n\n // Write content parts\n for (const part of parts) {\n if (part.content !== undefined) {\n zip.file(part.path, part.content);\n } else if (part.binaryContent !== undefined) {\n zip.file(part.path, part.binaryContent);\n }\n }\n\n // Add core properties if set\n if (coreProps) {\n const coreXml = buildCorePropertiesXml(coreProps);\n zip.file('docProps/core.xml', coreXml);\n\n // Add the generated relationship unless the caller already supplied it.\n if (\n !assembledRelationships.some(\n ({ sourcePart, relationship }) =>\n sourcePart === '' && relationship.type === REL_CORE_PROPERTIES,\n )\n ) {\n const ids = new Set(assembledRelationships.map(({ relationship }) => relationship.id));\n let nextId = 100;\n while (ids.has(`rId${nextId}`)) nextId++;\n assembledRelationships.push({\n sourcePart: '',\n relationship: {\n id: `rId${nextId}`,\n type: REL_CORE_PROPERTIES,\n target: 'docProps/core.xml',\n },\n });\n }\n }\n\n // Build and write [Content_Types].xml\n zip.file('[Content_Types].xml', buildContentTypesXml(parts, coreProps));\n\n // Build and write _rels/*.rels files\n const relsBySource = groupRelationshipsBySource(assembledRelationships);\n for (const [sourcePart, rels] of relsBySource) {\n const relsPath = sourcePart === '' ? '_rels/.rels' : buildRelsPath(sourcePart);\n zip.file(relsPath, buildRelationshipsXml(rels));\n }\n\n return zip;\n}\n\n// ============================================\n// [Content_Types].xml\n// ============================================\n\nfunction buildContentTypesXml(parts: PackagePart[], coreProps?: CoreProperties): string {\n const lines: string[] = [xmlDeclaration()];\n lines.push(`<Types xmlns=\"${NS_CONTENT_TYPES}\">`);\n\n // Default extension types\n const extensionTypes = new Map<string, string>();\n extensionTypes.set('rels', CONTENT_TYPE_RELATIONSHIPS);\n extensionTypes.set('xml', 'application/xml');\n\n // Collect extensions from binary parts (e.g., png, jpeg)\n for (const part of parts) {\n if (part.binaryContent !== undefined) {\n const ext = getExtension(part.path);\n if (ext && !extensionTypes.has(ext)) {\n extensionTypes.set(ext, part.contentType);\n }\n }\n }\n\n for (const [ext, ct] of extensionTypes) {\n lines.push(` <Default Extension=\"${escapeXml(ext)}\" ContentType=\"${escapeXml(ct)}\"/>`);\n }\n\n // Override entries for each XML part\n for (const part of parts) {\n if (part.content !== undefined) {\n lines.push(\n ` <Override PartName=\"/${escapeXml(part.path)}\" ContentType=\"${escapeXml(part.contentType)}\"/>`,\n );\n }\n }\n\n // Core properties override\n if (coreProps) {\n lines.push(\n ` <Override PartName=\"/docProps/core.xml\" ContentType=\"${CONTENT_TYPE_CORE_PROPERTIES}\"/>`,\n );\n }\n\n lines.push('</Types>');\n return lines.join('\\n');\n}\n\n// ============================================\n// Relationships XML\n// ============================================\n\nfunction buildRelationshipsXml(rels: Relationship[]): string {\n const lines: string[] = [xmlDeclaration()];\n lines.push(`<Relationships xmlns=\"${NS_RELATIONSHIPS}\">`);\n\n for (const rel of rels) {\n const attrs: string[] = [\n `Id=\"${escapeXml(rel.id)}\"`,\n `Type=\"${escapeXml(rel.type)}\"`,\n `Target=\"${escapeXml(rel.target)}\"`,\n ];\n if (rel.targetMode) {\n attrs.push(`TargetMode=\"${escapeXml(rel.targetMode)}\"`);\n }\n lines.push(` <Relationship ${attrs.join(' ')}/>`);\n }\n\n lines.push('</Relationships>');\n return lines.join('\\n');\n}\n\n// ============================================\n// Core Properties XML\n// ============================================\n\nfunction buildCorePropertiesXml(props: CoreProperties): string {\n const lines: string[] = [xmlDeclaration()];\n lines.push(\n `<cp:coreProperties` +\n ` xmlns:cp=\"${NS_CORE_PROPERTIES}\"` +\n ` xmlns:dc=\"${NS_DC}\"` +\n ` xmlns:dcterms=\"${NS_DCTERMS}\"` +\n ` xmlns:xsi=\"${NS_XSI}\">`,\n );\n\n if (props.title) lines.push(` <dc:title>${escapeXml(props.title)}</dc:title>`);\n if (props.subject) lines.push(` <dc:subject>${escapeXml(props.subject)}</dc:subject>`);\n if (props.creator) lines.push(` <dc:creator>${escapeXml(props.creator)}</dc:creator>`);\n if (props.description)\n lines.push(` <dc:description>${escapeXml(props.description)}</dc:description>`);\n if (props.keywords) lines.push(` <cp:keywords>${escapeXml(props.keywords)}</cp:keywords>`);\n if (props.lastModifiedBy)\n lines.push(` <cp:lastModifiedBy>${escapeXml(props.lastModifiedBy)}</cp:lastModifiedBy>`);\n if (props.revision) lines.push(` <cp:revision>${escapeXml(props.revision)}</cp:revision>`);\n if (props.created) {\n lines.push(\n ` <dcterms:created xsi:type=\"dcterms:W3CDTF\">${escapeXml(props.created)}</dcterms:created>`,\n );\n }\n if (props.modified) {\n lines.push(\n ` <dcterms:modified xsi:type=\"dcterms:W3CDTF\">${escapeXml(props.modified)}</dcterms:modified>`,\n );\n }\n\n lines.push('</cp:coreProperties>');\n return lines.join('\\n');\n}\n\n// ============================================\n// Helpers\n// ============================================\n\nfunction getExtension(path: string): string | undefined {\n const dot = path.lastIndexOf('.');\n if (dot === -1) return undefined;\n return path.substring(dot + 1).toLowerCase();\n}\n\nfunction buildRelsPath(partPath: string): string {\n const lastSlash = partPath.lastIndexOf('/');\n if (lastSlash === -1) {\n return `_rels/${partPath}.rels`;\n }\n const dir = partPath.substring(0, lastSlash);\n const file = partPath.substring(lastSlash + 1);\n return `${dir}/_rels/${file}.rels`;\n}\n\nfunction groupRelationshipsBySource(pending: PendingRelationship[]): Map<string, Relationship[]> {\n const grouped = new Map<string, Relationship[]>();\n for (const { sourcePart, relationship } of pending) {\n let list = grouped.get(sourcePart);\n if (!list) {\n list = [];\n grouped.set(sourcePart, list);\n }\n list.push(relationship);\n }\n return grouped;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
extractPlainText
|
|
3
3
|
} from "./chunk-PN52A5AA.js";
|
|
4
|
+
import {
|
|
5
|
+
createPackage
|
|
6
|
+
} from "./chunk-U32AG3G3.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-WC7WULGV.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-VJJM2SSH.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":"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","names":[]}
|