@bendyline/squisq-formats 2.1.0 → 2.2.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/LICENSE +21 -0
- package/NOTICE.md +20 -0
- package/README.md +1 -1
- package/dist/{chunk-NNHKUXKA.js → chunk-26ISNJ7Y.js} +85 -65
- package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
- package/dist/{chunk-WQSHGBLN.js → chunk-3NKXBZSR.js} +193 -42
- package/dist/{chunk-KURGXM4I.js → chunk-4V3KCHAP.js} +3 -4
- package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
- package/dist/{chunk-EW54IRRS.js → chunk-6S6GU3ZG.js} +5 -6
- package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
- package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
- package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
- package/dist/{chunk-SC67HYQJ.js → chunk-EJTNGKEA.js} +5 -8
- package/dist/chunk-GX7RAUME.js +121 -0
- package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
- package/dist/{chunk-RLU7UFYU.js → chunk-IPN56VLW.js} +83 -58
- package/dist/{chunk-DTDF6QDP.js → chunk-JE6LSIHE.js} +81 -20
- package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
- package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
- package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
- package/dist/chunk-OGS5VCGJ.js +446 -0
- package/dist/{chunk-GVS2XXV6.js → chunk-PJXJI2LY.js} +449 -57
- package/dist/{chunk-74GO3FVS.js → chunk-PU7REGWV.js} +5 -8
- package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
- package/dist/{chunk-QFLDYKCR.js → chunk-TAAENIRB.js} +5 -8
- package/dist/{chunk-7ARKUCQT.js → chunk-X2DEAXNK.js} +54 -2
- package/dist/container/index.js +1 -2
- package/dist/csv/index.d.ts +27 -2
- package/dist/csv/index.js +1 -2
- package/dist/docx/index.d.ts +5 -1
- package/dist/docx/index.js +9 -11
- package/dist/epub/index.d.ts +2 -0
- package/dist/epub/index.js +5 -6
- package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
- package/dist/extract-MN7LA3NL.js +13 -0
- package/dist/html/index.d.ts +11 -4
- package/dist/html/index.js +3 -4
- package/dist/images-ESPQKVTW.js +6 -0
- package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
- package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +28 -26
- package/dist/infer/index.d.ts +3 -3
- package/dist/infer/index.js +7 -9
- package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
- package/dist/layouts-DRWZGSPD.js +10 -0
- package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
- package/dist/ooxml/index.d.ts +3 -3
- package/dist/ooxml/index.js +14 -13
- package/dist/pdf/index.d.ts +18 -0
- package/dist/pdf/index.js +2 -3
- package/dist/pptx/index.d.ts +4 -4
- package/dist/pptx/index.js +11 -13
- package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
- package/dist/registry/index.d.ts +21 -5
- package/dist/registry/index.js +9 -6
- package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
- package/dist/xlsx/index.d.ts +3 -3
- package/dist/xlsx/index.js +6 -7
- package/package.json +6 -3
- package/dist/chunk-4VUWTSGM.js.map +0 -1
- package/dist/chunk-6M7Z25LA.js +0 -46
- package/dist/chunk-6M7Z25LA.js.map +0 -1
- package/dist/chunk-74GO3FVS.js.map +0 -1
- package/dist/chunk-7ARKUCQT.js.map +0 -1
- package/dist/chunk-DTDF6QDP.js.map +0 -1
- package/dist/chunk-EW54IRRS.js.map +0 -1
- package/dist/chunk-FE6OJV6O.js.map +0 -1
- package/dist/chunk-GVS2XXV6.js.map +0 -1
- package/dist/chunk-KURGXM4I.js.map +0 -1
- package/dist/chunk-MLX2BOJC.js.map +0 -1
- package/dist/chunk-NKAJPJ4G.js.map +0 -1
- package/dist/chunk-NNHKUXKA.js.map +0 -1
- package/dist/chunk-O3GVVND4.js.map +0 -1
- package/dist/chunk-ODL3SSPT.js.map +0 -1
- package/dist/chunk-PN52A5AA.js.map +0 -1
- package/dist/chunk-QFLDYKCR.js.map +0 -1
- package/dist/chunk-RFAPOKHJ.js.map +0 -1
- package/dist/chunk-RLU7UFYU.js.map +0 -1
- package/dist/chunk-SC67HYQJ.js.map +0 -1
- package/dist/chunk-SSUPBUF5.js.map +0 -1
- package/dist/chunk-U4MRIFKL.js.map +0 -1
- package/dist/chunk-UGYF5AZE.js +0 -275
- package/dist/chunk-UGYF5AZE.js.map +0 -1
- package/dist/chunk-WQSHGBLN.js.map +0 -1
- package/dist/chunk-YRT7GQ5Y.js +0 -28
- package/dist/chunk-YRT7GQ5Y.js.map +0 -1
- package/dist/container/index.js.map +0 -1
- package/dist/csv/index.js.map +0 -1
- package/dist/docx/index.js.map +0 -1
- package/dist/epub/index.js.map +0 -1
- package/dist/extract-OJ7ZQV6P.js +0 -15
- package/dist/extract-OJ7ZQV6P.js.map +0 -1
- package/dist/html/index.js.map +0 -1
- package/dist/images-7FBWPKE3.js +0 -7
- package/dist/images-7FBWPKE3.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/infer/index.js.map +0 -1
- package/dist/layouts-5VDIRPIJ.js +0 -12
- package/dist/layouts-5VDIRPIJ.js.map +0 -1
- package/dist/mapTheme-IR27S6IV.js.map +0 -1
- package/dist/ooxml/index.js.map +0 -1
- package/dist/pdf/index.js.map +0 -1
- package/dist/pptx/index.js.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/xlsx/index.js.map +0 -1
- package/src/__tests__/container.test.ts +0 -230
- package/src/__tests__/convert.test.ts +0 -495
- package/src/__tests__/csvImport.test.ts +0 -84
- package/src/__tests__/docxExport.test.ts +0 -491
- package/src/__tests__/docxImport.test.ts +0 -531
- package/src/__tests__/epub.test.ts +0 -649
- package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
- package/src/__tests__/formatRegistry.test.ts +0 -174
- package/src/__tests__/html.test.ts +0 -439
- package/src/__tests__/htmlImport.test.ts +0 -57
- package/src/__tests__/inferTheme.test.ts +0 -135
- package/src/__tests__/lossyWarnings.test.ts +0 -146
- package/src/__tests__/ooxml.test.ts +0 -271
- package/src/__tests__/ooxmlCancellation.test.ts +0 -113
- package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
- package/src/__tests__/pdfExport.test.ts +0 -322
- package/src/__tests__/pdfImport.test.ts +0 -384
- package/src/__tests__/plainHtml.test.ts +0 -417
- package/src/__tests__/plainHtmlBundle.test.ts +0 -253
- package/src/__tests__/pptxExport.test.ts +0 -138
- package/src/__tests__/pptxImport.test.ts +0 -145
- package/src/__tests__/pptxInferFixtures.ts +0 -314
- package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
- package/src/__tests__/roundTrip.test.ts +0 -201
- package/src/__tests__/roundTripAssets.test.ts +0 -50
- package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
- package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
- package/src/__tests__/roundTripMatrix.test.ts +0 -142
- package/src/__tests__/sharedContainer.test.ts +0 -41
- package/src/__tests__/sharedImages.test.ts +0 -61
- package/src/__tests__/xlsxExport.test.ts +0 -164
- package/src/__tests__/xlsxImport.test.ts +0 -80
- package/src/__tests__/zipSafety.test.ts +0 -317
- package/src/container/index.ts +0 -94
- package/src/csv/index.ts +0 -188
- package/src/docx/export.ts +0 -1375
- package/src/docx/import.ts +0 -1250
- package/src/docx/index.ts +0 -26
- package/src/docx/styles.ts +0 -145
- package/src/epub/export.ts +0 -968
- package/src/epub/index.ts +0 -20
- package/src/html/docsHtmlBundle.ts +0 -373
- package/src/html/htmlTemplate.ts +0 -385
- package/src/html/imageUtils.ts +0 -61
- package/src/html/import.ts +0 -297
- package/src/html/index.ts +0 -212
- package/src/html/plainHtml.ts +0 -790
- package/src/html/plainHtmlBundle.ts +0 -421
- package/src/index.ts +0 -109
- package/src/infer/extract.ts +0 -127
- package/src/infer/index.ts +0 -199
- package/src/infer/mapTheme.ts +0 -176
- package/src/infer/types.ts +0 -27
- package/src/ooxml/index.ts +0 -111
- package/src/ooxml/namespaces.ts +0 -217
- package/src/ooxml/readUtils.ts +0 -44
- package/src/ooxml/reader.ts +0 -318
- package/src/ooxml/themeReader.ts +0 -197
- package/src/ooxml/types.ts +0 -103
- package/src/ooxml/writer.ts +0 -339
- package/src/ooxml/xmlUtils.ts +0 -123
- package/src/pdf/export.ts +0 -1084
- package/src/pdf/import.ts +0 -1164
- package/src/pdf/index.ts +0 -29
- package/src/pdf/styles.ts +0 -180
- package/src/pptx/export.ts +0 -1184
- package/src/pptx/import.ts +0 -455
- package/src/pptx/index.ts +0 -52
- package/src/pptx/layouts.ts +0 -1222
- package/src/pptx/styles.ts +0 -96
- package/src/pptx/templates.ts +0 -187
- package/src/registry/convert.ts +0 -433
- package/src/registry/defaultFormats.ts +0 -413
- package/src/registry/errors.ts +0 -46
- package/src/registry/index.ts +0 -43
- package/src/registry/registry.ts +0 -48
- package/src/registry/types.ts +0 -170
- package/src/shared/boundedZipArchive.ts +0 -383
- package/src/shared/container.ts +0 -28
- package/src/shared/fidelity.ts +0 -130
- package/src/shared/images.ts +0 -44
- package/src/shared/inlineRuns.ts +0 -99
- package/src/shared/text.ts +0 -41
- package/src/shared/zipEntryCount.ts +0 -151
- package/src/shared/zipLimits.ts +0 -296
- package/src/shared/zipSafety.ts +0 -19
- package/src/xlsx/export.ts +0 -253
- package/src/xlsx/import.ts +0 -160
- package/src/xlsx/index.ts +0 -35
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Built-in format definitions.
|
|
3
|
-
*
|
|
4
|
-
* Each definition loads its converter module lazily via `import()` inside the
|
|
5
|
-
* method body, so importing the registry never eagerly bundles a heavy
|
|
6
|
-
* converter (pdf-lib, jszip, the OOXML writers, …). The capability of each
|
|
7
|
-
* format — which of import/export it supports — is expressed purely by which
|
|
8
|
-
* methods are present.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { ContentContainer } from '@bendyline/squisq/storage';
|
|
12
|
-
import type { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
13
|
-
import { ConversionError } from './errors.js';
|
|
14
|
-
import { markdownFidelityWarnings } from '../shared/fidelity.js';
|
|
15
|
-
import type {
|
|
16
|
-
ConversionResult,
|
|
17
|
-
BuiltinFormatOptions,
|
|
18
|
-
ConvertOptions,
|
|
19
|
-
FormatDefinition,
|
|
20
|
-
NormalizedInput,
|
|
21
|
-
} from './types.js';
|
|
22
|
-
|
|
23
|
-
// ── MIME constants ──────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
const MIME = {
|
|
26
|
-
md: 'text/markdown',
|
|
27
|
-
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
28
|
-
pdf: 'application/pdf',
|
|
29
|
-
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
30
|
-
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
31
|
-
csv: 'text/csv',
|
|
32
|
-
html: 'text/html',
|
|
33
|
-
zip: 'application/zip',
|
|
34
|
-
epub: 'application/epub+zip',
|
|
35
|
-
} as const;
|
|
36
|
-
|
|
37
|
-
// ── Small shared helpers ────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
/** Normalize any binary export return shape to Uint8Array. */
|
|
40
|
-
async function toBytes(data: ArrayBuffer | Uint8Array | Blob): Promise<Uint8Array> {
|
|
41
|
-
if (data instanceof Uint8Array) return data;
|
|
42
|
-
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
43
|
-
// Blob. Prefer the modern arrayBuffer() method; fall back to FileReader for
|
|
44
|
-
// environments (e.g. some jsdom builds) whose Blob lacks it.
|
|
45
|
-
if (typeof data.arrayBuffer === 'function') {
|
|
46
|
-
return new Uint8Array(await data.arrayBuffer());
|
|
47
|
-
}
|
|
48
|
-
const buffer = await new Promise<ArrayBuffer>((resolve, reject) => {
|
|
49
|
-
const reader = new FileReader();
|
|
50
|
-
reader.onload = () => resolve(reader.result as ArrayBuffer);
|
|
51
|
-
reader.onerror = () => reject(reader.error);
|
|
52
|
-
reader.readAsArrayBuffer(data);
|
|
53
|
-
});
|
|
54
|
-
return new Uint8Array(buffer);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** The theme to apply: explicit option wins, then the doc's own theme. */
|
|
58
|
-
function resolveThemeId(input: NormalizedInput, options: ConvertOptions): string | undefined {
|
|
59
|
-
return options.themeId ?? input.doc.themeId;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function optionsFor<K extends keyof BuiltinFormatOptions>(
|
|
63
|
-
options: ConvertOptions,
|
|
64
|
-
id: K,
|
|
65
|
-
): BuiltinFormatOptions[K] {
|
|
66
|
-
return (options.formatOptions?.[id] ?? {}) as BuiltinFormatOptions[K];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** The markdown shape an exporter needs — reuse the source's, else derive it. */
|
|
70
|
-
async function markdownOf(input: NormalizedInput): Promise<MarkdownDocument> {
|
|
71
|
-
if (input.markdownDoc) return input.markdownDoc;
|
|
72
|
-
const { docToMarkdown } = await import('@bendyline/squisq/doc');
|
|
73
|
-
return docToMarkdown(input.doc);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Collect image files from a container, keyed by both their container path and
|
|
78
|
-
* their bare filename. Exporters that resolve by url (PPTX) and the standalone
|
|
79
|
-
* player (HTML) both match against one of those two keys.
|
|
80
|
-
*/
|
|
81
|
-
async function collectContainerImages(
|
|
82
|
-
container: ContentContainer,
|
|
83
|
-
): Promise<Map<string, ArrayBuffer>> {
|
|
84
|
-
const images = new Map<string, ArrayBuffer>();
|
|
85
|
-
const files = await container.listFiles();
|
|
86
|
-
for (const file of files) {
|
|
87
|
-
if (!/\.(jpg|jpeg|png|gif|webp|svg|bmp|avif)$/i.test(file.path)) continue;
|
|
88
|
-
const data = await container.readFile(file.path);
|
|
89
|
-
if (!data) continue;
|
|
90
|
-
images.set(file.path, data);
|
|
91
|
-
const slash = file.path.lastIndexOf('/');
|
|
92
|
-
if (slash !== -1) images.set(file.path.slice(slash + 1), data);
|
|
93
|
-
}
|
|
94
|
-
return images;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function collectContainerDocxImages(
|
|
98
|
-
container: ContentContainer,
|
|
99
|
-
): Promise<Map<string, { data: ArrayBuffer; contentType: string }>> {
|
|
100
|
-
const images = await collectContainerImages(container);
|
|
101
|
-
const { extToMime } = await import('../shared/images.js');
|
|
102
|
-
return new Map(
|
|
103
|
-
[...images].map(([path, data]) => [
|
|
104
|
-
path,
|
|
105
|
-
{ data, contentType: extToMime(path.split('.').pop() ?? '') },
|
|
106
|
-
]),
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Copy a container and make its primary document match the normalized source. */
|
|
111
|
-
async function snapshotContainerWithDocument(input: NormalizedInput): Promise<ContentContainer> {
|
|
112
|
-
const { MemoryContentContainer } = await import('@bendyline/squisq/storage');
|
|
113
|
-
const snapshot = new MemoryContentContainer();
|
|
114
|
-
const entries = await input.container.listFiles();
|
|
115
|
-
for (const entry of entries) {
|
|
116
|
-
const data = await input.container.readFile(entry.path);
|
|
117
|
-
if (data) await snapshot.writeFile(entry.path, new Uint8Array(data), entry.mimeType);
|
|
118
|
-
}
|
|
119
|
-
const { stringifyMarkdown } = await import('@bendyline/squisq/markdown');
|
|
120
|
-
const documentPath = (await input.container.getDocumentPath()) ?? 'index.md';
|
|
121
|
-
await snapshot.writeDocument(stringifyMarkdown(await markdownOf(input)), documentPath);
|
|
122
|
-
return snapshot;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Resolve the player IIFE bundle or throw a helpful missing-dependency error. */
|
|
126
|
-
async function requirePlayerScript(options: ConvertOptions, format: string): Promise<string> {
|
|
127
|
-
const script = await options.resolvePlayerScript?.();
|
|
128
|
-
if (!script) {
|
|
129
|
-
throw new ConversionError('missing-dependency', 'HTML export needs a player script.', {
|
|
130
|
-
format,
|
|
131
|
-
hint: "pass resolvePlayerScript, e.g. () => import('@bendyline/squisq-react/standalone-source').then(m => m.PLAYER_BUNDLE)",
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
return script;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function ok(bytes: Uint8Array, mimeType: string, warnings: string[] = []): ConversionResult {
|
|
138
|
-
// suggestedFilename is filled in by convert() once the baseName is known.
|
|
139
|
-
return { bytes, mimeType, suggestedFilename: '', warnings };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Narrow the untyped per-format escape hatch (`options.formatOptions.pptx`)
|
|
144
|
-
* into the PPTX importer's options. Theme + layout inference default ON;
|
|
145
|
-
* only an explicit `false` disables them.
|
|
146
|
-
*/
|
|
147
|
-
function pptxImportOptionsFrom(options: ConvertOptions): BuiltinFormatOptions['pptx'] {
|
|
148
|
-
const raw = optionsFor(options, 'pptx');
|
|
149
|
-
return {
|
|
150
|
-
...raw,
|
|
151
|
-
inferTheme: raw?.inferTheme !== false,
|
|
152
|
-
inferLayouts: raw?.inferLayouts !== false,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ── Definitions ─────────────────────────────────────────────────────
|
|
157
|
-
|
|
158
|
-
export function defaultFormats(): FormatDefinition[] {
|
|
159
|
-
const md: FormatDefinition = {
|
|
160
|
-
id: 'md',
|
|
161
|
-
label: 'Markdown',
|
|
162
|
-
mimeType: MIME.md,
|
|
163
|
-
extensions: ['.md', '.markdown'],
|
|
164
|
-
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
165
|
-
const { parseMarkdown } = await import('@bendyline/squisq/markdown');
|
|
166
|
-
return parseMarkdown(new TextDecoder().decode(data), optionsFor(options, 'md').parse);
|
|
167
|
-
},
|
|
168
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
169
|
-
const { stringifyMarkdown } = await import('@bendyline/squisq/markdown');
|
|
170
|
-
const text = stringifyMarkdown(await markdownOf(input), optionsFor(options, 'md').stringify);
|
|
171
|
-
return ok(new TextEncoder().encode(text), MIME.md);
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const docx: FormatDefinition = {
|
|
176
|
-
id: 'docx',
|
|
177
|
-
label: 'Word (DOCX)',
|
|
178
|
-
mimeType: MIME.docx,
|
|
179
|
-
extensions: ['.docx'],
|
|
180
|
-
async importContainer(data, options): Promise<ContentContainer> {
|
|
181
|
-
const { docxToContainer } = await import('../docx/index.js');
|
|
182
|
-
return docxToContainer(data, optionsFor(options, 'docx'));
|
|
183
|
-
},
|
|
184
|
-
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
185
|
-
const { docxToMarkdownDoc } = await import('../docx/index.js');
|
|
186
|
-
return docxToMarkdownDoc(data, optionsFor(options, 'docx'));
|
|
187
|
-
},
|
|
188
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
189
|
-
const { markdownDocToDocx } = await import('../docx/index.js');
|
|
190
|
-
const raw = optionsFor(options, 'docx');
|
|
191
|
-
const markdownDoc = await markdownOf(input);
|
|
192
|
-
const containerImages = await collectContainerDocxImages(input.container);
|
|
193
|
-
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
194
|
-
const buf = await markdownDocToDocx(markdownDoc, {
|
|
195
|
-
...raw,
|
|
196
|
-
...(options.title !== undefined ? { title: options.title } : {}),
|
|
197
|
-
themeId: resolveThemeId(input, options),
|
|
198
|
-
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
199
|
-
images,
|
|
200
|
-
});
|
|
201
|
-
return ok(await toBytes(buf), MIME.docx, markdownFidelityWarnings(markdownDoc, 'docx'));
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const pdf: FormatDefinition = {
|
|
206
|
-
id: 'pdf',
|
|
207
|
-
label: 'PDF',
|
|
208
|
-
mimeType: MIME.pdf,
|
|
209
|
-
extensions: ['.pdf'],
|
|
210
|
-
async importContainer(data, options): Promise<ContentContainer> {
|
|
211
|
-
const { pdfToContainer } = await import('../pdf/index.js');
|
|
212
|
-
return pdfToContainer(data, optionsFor(options, 'pdf'));
|
|
213
|
-
},
|
|
214
|
-
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
215
|
-
const { pdfToMarkdownDoc } = await import('../pdf/index.js');
|
|
216
|
-
return pdfToMarkdownDoc(data, optionsFor(options, 'pdf'));
|
|
217
|
-
},
|
|
218
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
219
|
-
const { markdownDocToPdf } = await import('../pdf/index.js');
|
|
220
|
-
const raw = optionsFor(options, 'pdf');
|
|
221
|
-
const markdownDoc = await markdownOf(input);
|
|
222
|
-
const buf = await markdownDocToPdf(markdownDoc, {
|
|
223
|
-
...raw,
|
|
224
|
-
...(options.title !== undefined ? { title: options.title } : {}),
|
|
225
|
-
themeId: resolveThemeId(input, options),
|
|
226
|
-
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
227
|
-
});
|
|
228
|
-
return ok(await toBytes(buf), MIME.pdf, markdownFidelityWarnings(markdownDoc, 'pdf'));
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const pptx: FormatDefinition = {
|
|
233
|
-
id: 'pptx',
|
|
234
|
-
label: 'PowerPoint (PPTX)',
|
|
235
|
-
mimeType: MIME.pptx,
|
|
236
|
-
extensions: ['.pptx'],
|
|
237
|
-
async importContainer(data, options): Promise<ContentContainer> {
|
|
238
|
-
const { pptxToContainer } = await import('../pptx/index.js');
|
|
239
|
-
return pptxToContainer(data, pptxImportOptionsFrom(options));
|
|
240
|
-
},
|
|
241
|
-
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
242
|
-
const { pptxToMarkdownDoc } = await import('../pptx/index.js');
|
|
243
|
-
return pptxToMarkdownDoc(data, pptxImportOptionsFrom(options));
|
|
244
|
-
},
|
|
245
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
246
|
-
const { markdownDocToPptx } = await import('../pptx/index.js');
|
|
247
|
-
const raw = optionsFor(options, 'pptx');
|
|
248
|
-
const markdownDoc = await markdownOf(input);
|
|
249
|
-
const containerImages = await collectContainerImages(input.container);
|
|
250
|
-
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
251
|
-
const buf = await markdownDocToPptx(markdownDoc, {
|
|
252
|
-
...raw,
|
|
253
|
-
...(options.title !== undefined ? { title: options.title } : {}),
|
|
254
|
-
themeId: resolveThemeId(input, options),
|
|
255
|
-
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
256
|
-
images,
|
|
257
|
-
});
|
|
258
|
-
return ok(await toBytes(buf), MIME.pptx, markdownFidelityWarnings(markdownDoc, 'pptx'));
|
|
259
|
-
},
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
const xlsx: FormatDefinition = {
|
|
263
|
-
id: 'xlsx',
|
|
264
|
-
label: 'Excel (XLSX)',
|
|
265
|
-
mimeType: MIME.xlsx,
|
|
266
|
-
extensions: ['.xlsx'],
|
|
267
|
-
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
268
|
-
const { xlsxToMarkdownDoc } = await import('../xlsx/index.js');
|
|
269
|
-
return xlsxToMarkdownDoc(data, optionsFor(options, 'xlsx'));
|
|
270
|
-
},
|
|
271
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
272
|
-
const { markdownDocToXlsx } = await import('../xlsx/index.js');
|
|
273
|
-
const markdownDoc = await markdownOf(input);
|
|
274
|
-
// XLSX fidelity is tables-only: `table` nodes become worksheets and
|
|
275
|
-
// headings name them, but every other top-level block (prose, lists,
|
|
276
|
-
// images, code, …) is dropped. Count those and warn honestly.
|
|
277
|
-
const omitted = markdownDoc.children.filter(
|
|
278
|
-
(n) => n.type !== 'table' && n.type !== 'heading',
|
|
279
|
-
).length;
|
|
280
|
-
const warnings =
|
|
281
|
-
omitted > 0
|
|
282
|
-
? [`XLSX export is tables-only; ${omitted} non-table block(s) were omitted.`]
|
|
283
|
-
: [];
|
|
284
|
-
const blob = await markdownDocToXlsx(markdownDoc, {
|
|
285
|
-
...optionsFor(options, 'xlsx'),
|
|
286
|
-
...(options.title !== undefined ? { title: options.title } : {}),
|
|
287
|
-
});
|
|
288
|
-
return ok(await toBytes(blob), MIME.xlsx, warnings);
|
|
289
|
-
},
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const csv: FormatDefinition = {
|
|
293
|
-
id: 'csv',
|
|
294
|
-
label: 'CSV',
|
|
295
|
-
mimeType: MIME.csv,
|
|
296
|
-
extensions: ['.csv'],
|
|
297
|
-
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
298
|
-
const { csvToMarkdownDoc } = await import('../csv/index.js');
|
|
299
|
-
return csvToMarkdownDoc(data, optionsFor(options, 'csv'));
|
|
300
|
-
},
|
|
301
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
302
|
-
const { markdownDocToCsv } = await import('../csv/index.js');
|
|
303
|
-
const markdownDoc = await markdownOf(input);
|
|
304
|
-
const raw = optionsFor(options, 'csv');
|
|
305
|
-
const tableCount = markdownDoc.children.filter((n) => n.type === 'table').length;
|
|
306
|
-
const warnings: string[] = [];
|
|
307
|
-
if (tableCount > 1 && raw.tableIndex === undefined) {
|
|
308
|
-
warnings.push(
|
|
309
|
-
`Document has ${tableCount} tables; CSV export emitted only the first. Use the csv converter's tableIndex option to select another.`,
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
const text = markdownDocToCsv(markdownDoc, raw);
|
|
313
|
-
return ok(new TextEncoder().encode(text), MIME.csv, warnings);
|
|
314
|
-
},
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
const html: FormatDefinition = {
|
|
318
|
-
id: 'html',
|
|
319
|
-
label: 'HTML (single file)',
|
|
320
|
-
mimeType: MIME.html,
|
|
321
|
-
extensions: ['.html', '.htm'],
|
|
322
|
-
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
323
|
-
const { htmlToMarkdownDoc } = await import('../html/index.js');
|
|
324
|
-
return htmlToMarkdownDoc(data, optionsFor(options, 'html'));
|
|
325
|
-
},
|
|
326
|
-
// importContainer omitted: HTML import already inlines images as data URIs,
|
|
327
|
-
// so there is nothing to extract into a container this wave.
|
|
328
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
329
|
-
const playerScript = await requirePlayerScript(options, 'html');
|
|
330
|
-
const { docToHtml } = await import('../html/index.js');
|
|
331
|
-
const raw = optionsFor(options, 'html');
|
|
332
|
-
const containerImages = await collectContainerImages(input.container);
|
|
333
|
-
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
334
|
-
const htmlText = docToHtml(input.doc, {
|
|
335
|
-
...raw,
|
|
336
|
-
playerScript,
|
|
337
|
-
images,
|
|
338
|
-
title: options.title ?? input.baseName,
|
|
339
|
-
mode: raw.mode ?? 'static',
|
|
340
|
-
themeId: resolveThemeId(input, options),
|
|
341
|
-
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
342
|
-
});
|
|
343
|
-
return ok(new TextEncoder().encode(htmlText), MIME.html);
|
|
344
|
-
},
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
const htmlzip: FormatDefinition = {
|
|
348
|
-
id: 'htmlzip',
|
|
349
|
-
label: 'HTML (ZIP archive)',
|
|
350
|
-
mimeType: MIME.zip,
|
|
351
|
-
extensions: ['.html.zip'],
|
|
352
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
353
|
-
const playerScript = await requirePlayerScript(options, 'htmlzip');
|
|
354
|
-
const { docToHtmlZip } = await import('../html/index.js');
|
|
355
|
-
const raw = optionsFor(options, 'htmlzip');
|
|
356
|
-
const containerImages = await collectContainerImages(input.container);
|
|
357
|
-
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
358
|
-
const blob = await docToHtmlZip(input.doc, {
|
|
359
|
-
...raw,
|
|
360
|
-
playerScript,
|
|
361
|
-
images,
|
|
362
|
-
title: options.title ?? input.baseName,
|
|
363
|
-
mode: raw.mode ?? 'static',
|
|
364
|
-
themeId: resolveThemeId(input, options),
|
|
365
|
-
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
366
|
-
});
|
|
367
|
-
return ok(await toBytes(blob), MIME.zip);
|
|
368
|
-
},
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
const epub: FormatDefinition = {
|
|
372
|
-
id: 'epub',
|
|
373
|
-
label: 'EPUB',
|
|
374
|
-
mimeType: MIME.epub,
|
|
375
|
-
extensions: ['.epub'],
|
|
376
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
377
|
-
const { markdownDocToEpub } = await import('../epub/index.js');
|
|
378
|
-
const raw = optionsFor(options, 'epub');
|
|
379
|
-
const markdownDoc = await markdownOf(input);
|
|
380
|
-
const containerImages = await collectContainerImages(input.container);
|
|
381
|
-
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
382
|
-
const buf = await markdownDocToEpub(markdownDoc, {
|
|
383
|
-
...raw,
|
|
384
|
-
title: options.title ?? input.baseName,
|
|
385
|
-
themeId: resolveThemeId(input, options),
|
|
386
|
-
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
387
|
-
images,
|
|
388
|
-
});
|
|
389
|
-
return ok(await toBytes(buf), MIME.epub, markdownFidelityWarnings(markdownDoc, 'epub'));
|
|
390
|
-
},
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
const dbk: FormatDefinition = {
|
|
394
|
-
id: 'dbk',
|
|
395
|
-
label: 'Squisq container (DBK)',
|
|
396
|
-
mimeType: MIME.zip,
|
|
397
|
-
extensions: ['.dbk', '.zip'],
|
|
398
|
-
async importContainer(data, options): Promise<ContentContainer> {
|
|
399
|
-
const { zipToContainer } = await import('../container/index.js');
|
|
400
|
-
return zipToContainer(data, optionsFor(options, 'dbk'));
|
|
401
|
-
},
|
|
402
|
-
async exportDoc(input, options): Promise<ConversionResult> {
|
|
403
|
-
const { containerToZip } = await import('../container/index.js');
|
|
404
|
-
const blob = await containerToZip(
|
|
405
|
-
await snapshotContainerWithDocument(input),
|
|
406
|
-
optionsFor(options, 'dbk'),
|
|
407
|
-
);
|
|
408
|
-
return ok(await toBytes(blob), MIME.zip);
|
|
409
|
-
},
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
return [md, docx, pdf, pptx, xlsx, csv, html, htmlzip, epub, dbk];
|
|
413
|
-
}
|
package/src/registry/errors.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured error for the format registry / `convert()` pipeline.
|
|
3
|
-
*
|
|
4
|
-
* Every failure path in the registry throws a `ConversionError` with a stable
|
|
5
|
-
* machine-readable `code`, so callers can branch on the failure kind rather
|
|
6
|
-
* than string-matching messages.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { FormatId } from './types.js';
|
|
10
|
-
|
|
11
|
-
/** Machine-readable failure kinds surfaced by the registry / `convert()`. */
|
|
12
|
-
export type ConversionErrorCode =
|
|
13
|
-
| 'unknown-format'
|
|
14
|
-
| 'unsupported-input'
|
|
15
|
-
| 'unsupported-output'
|
|
16
|
-
| 'invalid-input'
|
|
17
|
-
| 'missing-dependency'
|
|
18
|
-
| 'conversion-failed';
|
|
19
|
-
|
|
20
|
-
/** Options for constructing a {@link ConversionError}. */
|
|
21
|
-
export interface ConversionErrorOptions {
|
|
22
|
-
/** The format id the error relates to, if any. */
|
|
23
|
-
format?: FormatId;
|
|
24
|
-
/** A human-oriented remediation hint. */
|
|
25
|
-
hint?: string;
|
|
26
|
-
/** The underlying error, preserved on `error.cause`. */
|
|
27
|
-
cause?: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** An error raised by the format registry or `convert()`. */
|
|
31
|
-
export class ConversionError extends Error {
|
|
32
|
-
readonly code: ConversionErrorCode;
|
|
33
|
-
readonly format?: FormatId;
|
|
34
|
-
readonly hint?: string;
|
|
35
|
-
|
|
36
|
-
constructor(code: ConversionErrorCode, message: string, opts: ConversionErrorOptions = {}) {
|
|
37
|
-
super(message);
|
|
38
|
-
this.name = 'ConversionError';
|
|
39
|
-
this.code = code;
|
|
40
|
-
this.format = opts.format;
|
|
41
|
-
this.hint = opts.hint;
|
|
42
|
-
if (opts.cause !== undefined) {
|
|
43
|
-
(this as Error & { cause?: unknown }).cause = opts.cause;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
package/src/registry/index.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format registry + programmatic `convert()` — @bendyline/squisq-formats/registry
|
|
3
|
-
*
|
|
4
|
-
* A small registry maps format ids (`md`, `docx`, `pdf`, `pptx`, `xlsx`, `csv`,
|
|
5
|
-
* `html`, `htmlzip`, `epub`, `dbk`) to `FormatDefinition`s that know how to
|
|
6
|
-
* import to / export from squisq's markdown + Doc model. `convert()` is the
|
|
7
|
-
* front door: give it a source and a target format id and it returns the
|
|
8
|
-
* encoded bytes plus a suggested filename.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { convert } from '@bendyline/squisq-formats/registry';
|
|
13
|
-
*
|
|
14
|
-
* const result = await convert(
|
|
15
|
-
* { kind: 'markdown', markdown: '# Hello', baseName: 'greeting' },
|
|
16
|
-
* 'docx',
|
|
17
|
-
* );
|
|
18
|
-
* // result.bytes, result.mimeType, result.suggestedFilename ('greeting.docx')
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export type {
|
|
23
|
-
FormatId,
|
|
24
|
-
ConversionResult,
|
|
25
|
-
NormalizedInput,
|
|
26
|
-
ConvertOptions,
|
|
27
|
-
FormatDefinition,
|
|
28
|
-
FormatRegistry,
|
|
29
|
-
ConvertSource,
|
|
30
|
-
BuiltinFormatOptions,
|
|
31
|
-
MarkdownFormatOptions,
|
|
32
|
-
DbkFormatOptions,
|
|
33
|
-
PreparedConversion,
|
|
34
|
-
PreparedExportOptions,
|
|
35
|
-
} from './types.js';
|
|
36
|
-
export { BUILTIN_FORMAT_IDS } from './types.js';
|
|
37
|
-
|
|
38
|
-
export { ConversionError } from './errors.js';
|
|
39
|
-
export type { ConversionErrorCode, ConversionErrorOptions } from './errors.js';
|
|
40
|
-
|
|
41
|
-
export { createRegistry, defaultRegistry } from './registry.js';
|
|
42
|
-
export { defaultFormats } from './defaultFormats.js';
|
|
43
|
-
export { convert, prepareConversion } from './convert.js';
|
package/src/registry/registry.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format registry implementation.
|
|
3
|
-
*
|
|
4
|
-
* `createRegistry()` returns an empty, mutable registry (last-write-wins by id);
|
|
5
|
-
* `defaultRegistry()` returns one preloaded with every built-in format.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { FormatDefinition, FormatId, FormatRegistry } from './types.js';
|
|
9
|
-
import { defaultFormats } from './defaultFormats.js';
|
|
10
|
-
|
|
11
|
-
/** Normalize an extension to lowercase, without a leading dot. */
|
|
12
|
-
function normalizeExt(ext: string): string {
|
|
13
|
-
return ext.replace(/^\.+/, '').toLowerCase();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Create an empty registry. Registering an existing id overwrites it. */
|
|
17
|
-
export function createRegistry(): FormatRegistry {
|
|
18
|
-
const byId = new Map<FormatId, FormatDefinition>();
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
register(def: FormatDefinition): void {
|
|
22
|
-
byId.set(def.id, def);
|
|
23
|
-
},
|
|
24
|
-
get(id: FormatId): FormatDefinition | undefined {
|
|
25
|
-
return byId.get(id);
|
|
26
|
-
},
|
|
27
|
-
byExtension(ext: string): FormatDefinition | undefined {
|
|
28
|
-
const target = normalizeExt(ext);
|
|
29
|
-
if (!target) return undefined;
|
|
30
|
-
for (const def of byId.values()) {
|
|
31
|
-
if (def.extensions.some((e) => normalizeExt(e) === target)) return def;
|
|
32
|
-
}
|
|
33
|
-
return undefined;
|
|
34
|
-
},
|
|
35
|
-
list(): FormatDefinition[] {
|
|
36
|
-
return [...byId.values()];
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Create a registry preloaded with all built-in formats. */
|
|
42
|
-
export function defaultRegistry(): FormatRegistry {
|
|
43
|
-
const registry = createRegistry();
|
|
44
|
-
for (const def of defaultFormats()) {
|
|
45
|
-
registry.register(def);
|
|
46
|
-
}
|
|
47
|
-
return registry;
|
|
48
|
-
}
|