@bendyline/squisq-formats 1.4.0 → 1.4.2
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 +180 -43
- package/dist/{chunk-CRTC6DEX.js → chunk-46FMDOWZ.js} +2 -2
- package/dist/{chunk-ON232HJR.js → chunk-5LSSWZPU.js} +2 -2
- package/dist/{chunk-APSIAEXL.js → chunk-D7IFPWNZ.js} +87 -43
- package/dist/chunk-D7IFPWNZ.js.map +1 -0
- package/dist/chunk-DQAZR57U.js +33 -0
- package/dist/chunk-DQAZR57U.js.map +1 -0
- package/dist/chunk-EHLGMSTE.js +273 -0
- package/dist/chunk-EHLGMSTE.js.map +1 -0
- package/dist/{chunk-A3FHLTY5.js → chunk-MJGRI6XR.js} +6 -19
- package/dist/chunk-MJGRI6XR.js.map +1 -0
- package/dist/{chunk-JBNVE2GV.js → chunk-RFAPOKHJ.js} +9 -2
- package/dist/chunk-RFAPOKHJ.js.map +1 -0
- package/dist/chunk-RTS5XBZ7.js +526 -0
- package/dist/chunk-RTS5XBZ7.js.map +1 -0
- package/dist/{chunk-FMOIGA5T.js → chunk-SHLPSADL.js} +178 -5
- package/dist/chunk-SHLPSADL.js.map +1 -0
- package/dist/chunk-SSUPBUF5.js +15 -0
- package/dist/chunk-SSUPBUF5.js.map +1 -0
- package/dist/{chunk-BJQIJ4S4.js → chunk-WWBIKRNP.js} +17 -41
- package/dist/chunk-WWBIKRNP.js.map +1 -0
- package/dist/{chunk-I2GFTEOV.js → chunk-YESS7VY3.js} +77 -15
- package/dist/{chunk-I2GFTEOV.js.map → chunk-YESS7VY3.js.map} +1 -1
- package/dist/csv/index.d.ts +14 -1
- package/dist/csv/index.js +1 -1
- package/dist/docx/index.js +4 -3
- package/dist/epub/index.js +3 -2
- package/dist/html/index.d.ts +3 -0
- package/dist/html/index.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +26 -11
- package/dist/ooxml/index.d.ts +5 -1
- package/dist/ooxml/index.js +16 -12
- package/dist/pdf/index.js +2 -1
- package/dist/pptx/index.d.ts +18 -3
- package/dist/pptx/index.js +7 -4
- package/dist/registry/index.d.ts +167 -0
- package/dist/registry/index.js +17 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/xlsx/index.d.ts +44 -14
- package/dist/xlsx/index.js +4 -2
- package/package.json +7 -2
- package/src/__tests__/convert.test.ts +186 -0
- package/src/__tests__/csvImport.test.ts +34 -1
- package/src/__tests__/formatRegistry.test.ts +174 -0
- package/src/__tests__/lossyWarnings.test.ts +105 -0
- package/src/__tests__/pdfImport.test.ts +95 -1
- package/src/__tests__/pptxImport.test.ts +46 -1
- package/src/__tests__/roundTripAssets.test.ts +50 -0
- package/src/__tests__/roundTripMatrix.test.ts +10 -4
- package/src/__tests__/sharedContainer.test.ts +41 -0
- package/src/__tests__/sharedImages.test.ts +61 -0
- package/src/__tests__/xlsxExport.test.ts +164 -0
- package/src/csv/index.ts +25 -2
- package/src/docx/import.ts +6 -37
- package/src/html/imageUtils.ts +5 -19
- package/src/index.ts +29 -5
- package/src/ooxml/index.ts +3 -0
- package/src/ooxml/namespaces.ts +10 -0
- package/src/pdf/import.ts +145 -50
- package/src/pptx/import.ts +127 -10
- package/src/pptx/index.ts +1 -1
- package/src/registry/convert.ts +318 -0
- package/src/registry/defaultFormats.ts +323 -0
- package/src/registry/errors.ts +46 -0
- package/src/registry/index.ts +38 -0
- package/src/registry/registry.ts +48 -0
- package/src/registry/types.ts +107 -0
- package/src/shared/container.ts +28 -0
- package/src/shared/images.ts +44 -0
- package/src/xlsx/export.ts +253 -0
- package/src/xlsx/index.ts +10 -36
- package/dist/chunk-A3FHLTY5.js.map +0 -1
- package/dist/chunk-APSIAEXL.js.map +0 -1
- package/dist/chunk-BJQIJ4S4.js.map +0 -1
- package/dist/chunk-FMOIGA5T.js.map +0 -1
- package/dist/chunk-JBNVE2GV.js.map +0 -1
- package/dist/chunk-L53YIGYS.js +0 -141
- package/dist/chunk-L53YIGYS.js.map +0 -1
- package/dist/chunk-QSE7EWE7.js +0 -181
- package/dist/chunk-QSE7EWE7.js.map +0 -1
- /package/dist/{chunk-CRTC6DEX.js.map → chunk-46FMDOWZ.js.map} +0 -0
- /package/dist/{chunk-ON232HJR.js.map → chunk-5LSSWZPU.js.map} +0 -0
|
@@ -1,23 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extToMime
|
|
3
|
+
} from "./chunk-DQAZR57U.js";
|
|
4
|
+
|
|
1
5
|
// src/html/imageUtils.ts
|
|
2
|
-
var MIME_MAP = {
|
|
3
|
-
jpg: "image/jpeg",
|
|
4
|
-
jpeg: "image/jpeg",
|
|
5
|
-
png: "image/png",
|
|
6
|
-
gif: "image/gif",
|
|
7
|
-
webp: "image/webp",
|
|
8
|
-
svg: "image/svg+xml",
|
|
9
|
-
ico: "image/x-icon",
|
|
10
|
-
bmp: "image/bmp",
|
|
11
|
-
avif: "image/avif",
|
|
12
|
-
mp3: "audio/mpeg",
|
|
13
|
-
wav: "audio/wav",
|
|
14
|
-
ogg: "audio/ogg",
|
|
15
|
-
mp4: "video/mp4",
|
|
16
|
-
webm: "video/webm"
|
|
17
|
-
};
|
|
18
6
|
function inferMimeType(filename) {
|
|
19
|
-
|
|
20
|
-
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
7
|
+
return extToMime(filename.split(".").pop() ?? "");
|
|
21
8
|
}
|
|
22
9
|
function arrayBufferToBase64DataUrl(buffer, mimeType) {
|
|
23
10
|
const bytes = new Uint8Array(buffer);
|
|
@@ -39,4 +26,4 @@ export {
|
|
|
39
26
|
arrayBufferToBase64DataUrl,
|
|
40
27
|
extractFilename
|
|
41
28
|
};
|
|
42
|
-
//# sourceMappingURL=chunk-
|
|
29
|
+
//# sourceMappingURL=chunk-MJGRI6XR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/html/imageUtils.ts"],"sourcesContent":["/**\n * Image Utilities for HTML Export\n *\n * Browser-compatible helpers for converting image data to base64 data URIs\n * and inferring MIME types from filenames.\n */\n\nimport { extToMime } from '../shared/images.js';\n\n/**\n * Infer a MIME type from a filename's extension.\n * Returns 'application/octet-stream' for unknown types.\n *\n * Thin wrapper over the shared {@link extToMime} map so html export, docx\n * import, and pptx import all agree on extension → MIME.\n */\nexport function inferMimeType(filename: string): string {\n return extToMime(filename.split('.').pop() ?? '');\n}\n\n/**\n * Convert an ArrayBuffer to a base64-encoded data URI string.\n *\n * @param buffer - The binary image data\n * @param mimeType - MIME type (e.g., 'image/jpeg'). If not provided, defaults to\n * 'application/octet-stream'.\n * @returns A `data:` URI string\n */\nexport function arrayBufferToBase64DataUrl(buffer: ArrayBuffer, mimeType: string): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n return `data:${mimeType};base64,${base64}`;\n}\n\n/**\n * Extract the filename from a path or URL (strips directory and query).\n *\n * @example\n * extractFilename('images/hero.jpg') // 'hero.jpg'\n * extractFilename('https://example.com/photo.png?v=2') // 'photo.png'\n */\nexport function extractFilename(path: string): string {\n // Strip query/hash\n const clean = path.split('?')[0].split('#')[0];\n // Get last segment\n const parts = clean.split('/');\n return parts[parts.length - 1] || path;\n}\n"],"mappings":";;;;;AAgBO,SAAS,cAAc,UAA0B;AACtD,SAAO,UAAU,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE;AAClD;AAUO,SAAS,2BAA2B,QAAqB,UAA0B;AACxF,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,QAAM,SAAS,KAAK,MAAM;AAC1B,SAAO,QAAQ,QAAQ,WAAW,MAAM;AAC1C;AASO,SAAS,gBAAgB,MAAsB;AAEpD,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC;AAE7C,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,SAAO,MAAM,MAAM,SAAS,CAAC,KAAK;AACpC;","names":[]}
|
|
@@ -100,7 +100,14 @@ function cellText(cell) {
|
|
|
100
100
|
}
|
|
101
101
|
function markdownDocToCsv(doc, options = {}) {
|
|
102
102
|
const delimiter = options.delimiter ?? ",";
|
|
103
|
-
const
|
|
103
|
+
const tables = doc.children.filter((n) => n.type === "table");
|
|
104
|
+
const index = options.tableIndex ?? 0;
|
|
105
|
+
if (options.tableIndex !== void 0 && (!Number.isInteger(index) || index < 0 || index >= tables.length)) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`CSV export: tableIndex ${index} is out of range \u2014 the document contains ${tables.length} table(s).`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const table = tables[index];
|
|
104
111
|
if (!table) return "";
|
|
105
112
|
return table.children.map(
|
|
106
113
|
(row) => row.children.map((cell) => escapeCsvField(cellText(cell), delimiter)).join(delimiter)
|
|
@@ -113,4 +120,4 @@ export {
|
|
|
113
120
|
csvToDoc,
|
|
114
121
|
markdownDocToCsv
|
|
115
122
|
};
|
|
116
|
-
//# sourceMappingURL=chunk-
|
|
123
|
+
//# sourceMappingURL=chunk-RFAPOKHJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/csv/index.ts"],"sourcesContent":["/**\n * @bendyline/squisq-formats CSV Module\n *\n * Bridges CSV ↔ the squisq markdown table model. CSV isn't OOXML, so this\n * module is self-contained (no jszip / DOMParser): a small RFC-4180 parser on\n * the import side and a serializer on the export side. The first row is treated\n * as the table header by default.\n *\n * @example\n * ```ts\n * import { csvToMarkdownDoc, markdownDocToCsv } from '@bendyline/squisq-formats/csv';\n * ```\n */\n\nimport { markdownToDoc } from '@bendyline/squisq/doc';\nimport type {\n MarkdownDocument,\n MarkdownTable,\n MarkdownTableCell,\n MarkdownTableRow,\n} from '@bendyline/squisq/markdown';\nimport type { Doc } from '@bendyline/squisq/schemas';\n\nexport interface CsvImportOptions {\n /** Field delimiter. Default `,`. */\n delimiter?: string;\n /** Treat the first row as a header row. Default true. */\n hasHeader?: boolean;\n}\n\nexport interface CsvExportOptions {\n /** Field delimiter. Default `,`. */\n delimiter?: string;\n /**\n * Zero-based index of the table to export when the document contains more\n * than one. Default 0 (the first table). An explicitly provided index that\n * doesn't match a table in the document is an error.\n */\n tableIndex?: number;\n}\n\nasync function toText(data: ArrayBuffer | Blob | string): Promise<string> {\n if (typeof data === 'string') return data;\n if (typeof Blob !== 'undefined' && data instanceof Blob) return data.text();\n return new TextDecoder().decode(new Uint8Array(data as ArrayBuffer));\n}\n\n/** Parse CSV text into a grid of string cells (RFC 4180: quotes, escaped quotes). */\nexport function parseCsv(text: string, delimiter = ','): string[][] {\n const rows: string[][] = [];\n let row: string[] = [];\n let field = '';\n let inQuotes = false;\n let sawAny = false;\n const pushField = () => {\n row.push(field);\n field = '';\n };\n const pushRow = () => {\n pushField();\n rows.push(row);\n row = [];\n };\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!;\n if (inQuotes) {\n if (ch === '\"') {\n if (text[i + 1] === '\"') {\n field += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n field += ch;\n }\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n sawAny = true;\n continue;\n }\n if (ch === delimiter) {\n sawAny = true;\n pushField();\n continue;\n }\n if (ch === '\\n' || ch === '\\r') {\n if (ch === '\\r' && text[i + 1] === '\\n') i++;\n pushRow();\n sawAny = false;\n continue;\n }\n sawAny = true;\n field += ch;\n }\n // Flush a trailing field/row only if the last line wasn't terminated.\n if (field !== '' || row.length > 0 || sawAny) pushRow();\n return rows;\n}\n\nfunction rowsToTable(rows: string[][], hasHeader: boolean): MarkdownTable {\n const maxCols = rows.reduce((m, r) => Math.max(m, r.length), 1);\n const mdRows: MarkdownTableRow[] = rows.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 ...(hasHeader && rowIdx === 0 ? { isHeader: true } : {}),\n children: value ? [{ type: 'text', value }] : [],\n });\n }\n return { type: 'tableRow', children };\n });\n return { type: 'table', children: mdRows };\n}\n\n/** Convert CSV to a MarkdownDocument containing a single table. */\nexport async function csvToMarkdownDoc(\n data: ArrayBuffer | Blob | string,\n options: CsvImportOptions = {},\n): Promise<MarkdownDocument> {\n const text = await toText(data);\n const rows = parseCsv(text, options.delimiter ?? ',');\n const hasHeader = options.hasHeader ?? true;\n const children = rows.length > 0 ? [rowsToTable(rows, hasHeader)] : [];\n return { type: 'document', children };\n}\n\n/** Convert CSV to a squisq Doc. */\nexport async function csvToDoc(\n data: ArrayBuffer | Blob | string,\n options: CsvImportOptions = {},\n): Promise<Doc> {\n return markdownToDoc(await csvToMarkdownDoc(data, options));\n}\n\nfunction escapeCsvField(value: string, delimiter: string): string {\n if (value.includes('\"') || value.includes(delimiter) || /[\\r\\n]/.test(value)) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nfunction cellText(cell: MarkdownTableCell): string {\n // Flatten inline children to plain text (CSV has no formatting).\n const walk = (nodes: unknown[]): string =>\n nodes\n .map((n) => {\n const node = n as { type?: string; value?: string; children?: unknown[] };\n if (node.value !== undefined) return node.value;\n if (Array.isArray(node.children)) return walk(node.children);\n return '';\n })\n .join('');\n return walk(cell.children);\n}\n\n/**\n * Serialize one table in a MarkdownDocument to CSV text.\n *\n * By default the first table is exported. Documents with multiple tables can\n * select another via `options.tableIndex` (zero-based). An explicit\n * `tableIndex` that is out of range throws; the implicit first-table default\n * on a table-less document returns an empty string (back-compat).\n */\nexport function markdownDocToCsv(doc: MarkdownDocument, options: CsvExportOptions = {}): string {\n const delimiter = options.delimiter ?? ',';\n const tables = doc.children.filter((n): n is MarkdownTable => n.type === 'table');\n const index = options.tableIndex ?? 0;\n if (\n options.tableIndex !== undefined &&\n (!Number.isInteger(index) || index < 0 || index >= tables.length)\n ) {\n throw new Error(\n `CSV export: tableIndex ${index} is out of range — the document contains ${tables.length} table(s).`,\n );\n }\n const table = tables[index];\n if (!table) return '';\n return table.children\n .map((row) =>\n row.children.map((cell) => escapeCsvField(cellText(cell), delimiter)).join(delimiter),\n )\n .join('\\r\\n');\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
// src/registry/types.ts
|
|
2
|
+
var BUILTIN_FORMAT_IDS = [
|
|
3
|
+
"md",
|
|
4
|
+
"docx",
|
|
5
|
+
"pdf",
|
|
6
|
+
"pptx",
|
|
7
|
+
"xlsx",
|
|
8
|
+
"csv",
|
|
9
|
+
"html",
|
|
10
|
+
"htmlzip",
|
|
11
|
+
"epub",
|
|
12
|
+
"dbk"
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// src/registry/errors.ts
|
|
16
|
+
var ConversionError = class extends Error {
|
|
17
|
+
constructor(code, message, opts = {}) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "ConversionError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.format = opts.format;
|
|
22
|
+
this.hint = opts.hint;
|
|
23
|
+
if (opts.cause !== void 0) {
|
|
24
|
+
this.cause = opts.cause;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/registry/defaultFormats.ts
|
|
30
|
+
var MIME = {
|
|
31
|
+
md: "text/markdown",
|
|
32
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
33
|
+
pdf: "application/pdf",
|
|
34
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
35
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
36
|
+
csv: "text/csv",
|
|
37
|
+
html: "text/html",
|
|
38
|
+
zip: "application/zip",
|
|
39
|
+
epub: "application/epub+zip"
|
|
40
|
+
};
|
|
41
|
+
async function toBytes(data) {
|
|
42
|
+
if (data instanceof Uint8Array) return data;
|
|
43
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
44
|
+
if (typeof data.arrayBuffer === "function") {
|
|
45
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
46
|
+
}
|
|
47
|
+
const buffer = await new Promise((resolve, reject) => {
|
|
48
|
+
const reader = new FileReader();
|
|
49
|
+
reader.onload = () => resolve(reader.result);
|
|
50
|
+
reader.onerror = () => reject(reader.error);
|
|
51
|
+
reader.readAsArrayBuffer(data);
|
|
52
|
+
});
|
|
53
|
+
return new Uint8Array(buffer);
|
|
54
|
+
}
|
|
55
|
+
function resolveThemeId(input, options) {
|
|
56
|
+
return options.themeId ?? input.doc.themeId;
|
|
57
|
+
}
|
|
58
|
+
async function markdownOf(input) {
|
|
59
|
+
if (input.markdownDoc) return input.markdownDoc;
|
|
60
|
+
const { docToMarkdown } = await import("@bendyline/squisq/doc");
|
|
61
|
+
return docToMarkdown(input.doc);
|
|
62
|
+
}
|
|
63
|
+
async function collectContainerImages(container) {
|
|
64
|
+
const images = /* @__PURE__ */ new Map();
|
|
65
|
+
const files = await container.listFiles();
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
if (!/\.(jpg|jpeg|png|gif|webp|svg|bmp|avif)$/i.test(file.path)) continue;
|
|
68
|
+
const data = await container.readFile(file.path);
|
|
69
|
+
if (!data) continue;
|
|
70
|
+
images.set(file.path, data);
|
|
71
|
+
const slash = file.path.lastIndexOf("/");
|
|
72
|
+
if (slash !== -1) images.set(file.path.slice(slash + 1), data);
|
|
73
|
+
}
|
|
74
|
+
return images;
|
|
75
|
+
}
|
|
76
|
+
async function requirePlayerScript(options, format) {
|
|
77
|
+
const script = await options.resolvePlayerScript?.();
|
|
78
|
+
if (!script) {
|
|
79
|
+
throw new ConversionError("missing-dependency", "HTML export needs a player script.", {
|
|
80
|
+
format,
|
|
81
|
+
hint: "pass resolvePlayerScript, e.g. () => import('@bendyline/squisq-react/standalone-source').then(m => m.PLAYER_BUNDLE)"
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return script;
|
|
85
|
+
}
|
|
86
|
+
function ok(bytes, mimeType, warnings = []) {
|
|
87
|
+
return { bytes, mimeType, suggestedFilename: "", warnings };
|
|
88
|
+
}
|
|
89
|
+
function defaultFormats() {
|
|
90
|
+
const md = {
|
|
91
|
+
id: "md",
|
|
92
|
+
label: "Markdown",
|
|
93
|
+
mimeType: MIME.md,
|
|
94
|
+
extensions: [".md", ".markdown"],
|
|
95
|
+
async importDoc(data) {
|
|
96
|
+
const { parseMarkdown } = await import("@bendyline/squisq/markdown");
|
|
97
|
+
return parseMarkdown(new TextDecoder().decode(data));
|
|
98
|
+
},
|
|
99
|
+
async exportDoc(input) {
|
|
100
|
+
const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
|
|
101
|
+
const text = stringifyMarkdown(await markdownOf(input));
|
|
102
|
+
return ok(new TextEncoder().encode(text), MIME.md);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const docx = {
|
|
106
|
+
id: "docx",
|
|
107
|
+
label: "Word (DOCX)",
|
|
108
|
+
mimeType: MIME.docx,
|
|
109
|
+
extensions: [".docx"],
|
|
110
|
+
async importContainer(data) {
|
|
111
|
+
const { docxToContainer } = await import("./docx/index.js");
|
|
112
|
+
return docxToContainer(data);
|
|
113
|
+
},
|
|
114
|
+
async importDoc(data) {
|
|
115
|
+
const { docxToMarkdownDoc } = await import("./docx/index.js");
|
|
116
|
+
return docxToMarkdownDoc(data);
|
|
117
|
+
},
|
|
118
|
+
async exportDoc(input, options) {
|
|
119
|
+
const { markdownDocToDocx } = await import("./docx/index.js");
|
|
120
|
+
const buf = await markdownDocToDocx(await markdownOf(input), {
|
|
121
|
+
themeId: resolveThemeId(input, options)
|
|
122
|
+
});
|
|
123
|
+
return ok(await toBytes(buf), MIME.docx);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const pdf = {
|
|
127
|
+
id: "pdf",
|
|
128
|
+
label: "PDF",
|
|
129
|
+
mimeType: MIME.pdf,
|
|
130
|
+
extensions: [".pdf"],
|
|
131
|
+
async importContainer(data) {
|
|
132
|
+
const { pdfToContainer } = await import("./pdf/index.js");
|
|
133
|
+
return pdfToContainer(data);
|
|
134
|
+
},
|
|
135
|
+
async importDoc(data) {
|
|
136
|
+
const { pdfToMarkdownDoc } = await import("./pdf/index.js");
|
|
137
|
+
return pdfToMarkdownDoc(data);
|
|
138
|
+
},
|
|
139
|
+
async exportDoc(input, options) {
|
|
140
|
+
const { markdownDocToPdf } = await import("./pdf/index.js");
|
|
141
|
+
const buf = await markdownDocToPdf(await markdownOf(input), {
|
|
142
|
+
themeId: resolveThemeId(input, options)
|
|
143
|
+
});
|
|
144
|
+
return ok(await toBytes(buf), MIME.pdf);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const pptx = {
|
|
148
|
+
id: "pptx",
|
|
149
|
+
label: "PowerPoint (PPTX)",
|
|
150
|
+
mimeType: MIME.pptx,
|
|
151
|
+
extensions: [".pptx"],
|
|
152
|
+
async importContainer(data) {
|
|
153
|
+
const { pptxToContainer } = await import("./pptx/index.js");
|
|
154
|
+
return pptxToContainer(data);
|
|
155
|
+
},
|
|
156
|
+
async importDoc(data) {
|
|
157
|
+
const { pptxToMarkdownDoc } = await import("./pptx/index.js");
|
|
158
|
+
return pptxToMarkdownDoc(data);
|
|
159
|
+
},
|
|
160
|
+
async exportDoc(input, options) {
|
|
161
|
+
const { markdownDocToPptx } = await import("./pptx/index.js");
|
|
162
|
+
const images = await collectContainerImages(input.container);
|
|
163
|
+
const buf = await markdownDocToPptx(await markdownOf(input), {
|
|
164
|
+
themeId: resolveThemeId(input, options),
|
|
165
|
+
images
|
|
166
|
+
});
|
|
167
|
+
return ok(await toBytes(buf), MIME.pptx);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const xlsx = {
|
|
171
|
+
id: "xlsx",
|
|
172
|
+
label: "Excel (XLSX)",
|
|
173
|
+
mimeType: MIME.xlsx,
|
|
174
|
+
extensions: [".xlsx"],
|
|
175
|
+
async importDoc(data) {
|
|
176
|
+
const { xlsxToMarkdownDoc } = await import("./xlsx/index.js");
|
|
177
|
+
return xlsxToMarkdownDoc(data);
|
|
178
|
+
},
|
|
179
|
+
async exportDoc(input) {
|
|
180
|
+
const { markdownDocToXlsx } = await import("./xlsx/index.js");
|
|
181
|
+
const markdownDoc = await markdownOf(input);
|
|
182
|
+
const omitted = markdownDoc.children.filter(
|
|
183
|
+
(n) => n.type !== "table" && n.type !== "heading"
|
|
184
|
+
).length;
|
|
185
|
+
const warnings = omitted > 0 ? [`XLSX export is tables-only; ${omitted} non-table block(s) were omitted.`] : [];
|
|
186
|
+
const blob = await markdownDocToXlsx(markdownDoc);
|
|
187
|
+
return ok(await toBytes(blob), MIME.xlsx, warnings);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const csv = {
|
|
191
|
+
id: "csv",
|
|
192
|
+
label: "CSV",
|
|
193
|
+
mimeType: MIME.csv,
|
|
194
|
+
extensions: [".csv"],
|
|
195
|
+
async importDoc(data) {
|
|
196
|
+
const { csvToMarkdownDoc } = await import("./csv/index.js");
|
|
197
|
+
return csvToMarkdownDoc(data);
|
|
198
|
+
},
|
|
199
|
+
async exportDoc(input) {
|
|
200
|
+
const { markdownDocToCsv } = await import("./csv/index.js");
|
|
201
|
+
const markdownDoc = await markdownOf(input);
|
|
202
|
+
const tableCount = markdownDoc.children.filter((n) => n.type === "table").length;
|
|
203
|
+
const warnings = [];
|
|
204
|
+
if (tableCount > 1) {
|
|
205
|
+
warnings.push(
|
|
206
|
+
`Document has ${tableCount} tables; CSV export emitted only the first. Use the csv converter's tableIndex option to select another.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const text = markdownDocToCsv(markdownDoc);
|
|
210
|
+
return ok(new TextEncoder().encode(text), MIME.csv, warnings);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const html = {
|
|
214
|
+
id: "html",
|
|
215
|
+
label: "HTML (single file)",
|
|
216
|
+
mimeType: MIME.html,
|
|
217
|
+
extensions: [".html", ".htm"],
|
|
218
|
+
async importDoc(data) {
|
|
219
|
+
const { htmlToMarkdownDoc } = await import("./html/index.js");
|
|
220
|
+
return htmlToMarkdownDoc(data);
|
|
221
|
+
},
|
|
222
|
+
// importContainer omitted: HTML import already inlines images as data URIs,
|
|
223
|
+
// so there is nothing to extract into a container this wave.
|
|
224
|
+
async exportDoc(input, options) {
|
|
225
|
+
const playerScript = await requirePlayerScript(options, "html");
|
|
226
|
+
const { docToHtml } = await import("./html/index.js");
|
|
227
|
+
const images = await collectContainerImages(input.container);
|
|
228
|
+
const htmlText = docToHtml(input.doc, {
|
|
229
|
+
playerScript,
|
|
230
|
+
images,
|
|
231
|
+
title: options.title ?? input.baseName,
|
|
232
|
+
mode: "static",
|
|
233
|
+
themeId: resolveThemeId(input, options)
|
|
234
|
+
});
|
|
235
|
+
return ok(new TextEncoder().encode(htmlText), MIME.html);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const htmlzip = {
|
|
239
|
+
id: "htmlzip",
|
|
240
|
+
label: "HTML (ZIP archive)",
|
|
241
|
+
mimeType: MIME.zip,
|
|
242
|
+
extensions: [".html.zip"],
|
|
243
|
+
async exportDoc(input, options) {
|
|
244
|
+
const playerScript = await requirePlayerScript(options, "htmlzip");
|
|
245
|
+
const { docToHtmlZip } = await import("./html/index.js");
|
|
246
|
+
const images = await collectContainerImages(input.container);
|
|
247
|
+
const blob = await docToHtmlZip(input.doc, {
|
|
248
|
+
playerScript,
|
|
249
|
+
images,
|
|
250
|
+
title: options.title ?? input.baseName,
|
|
251
|
+
mode: "static",
|
|
252
|
+
themeId: resolveThemeId(input, options)
|
|
253
|
+
});
|
|
254
|
+
return ok(await toBytes(blob), MIME.zip);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const epub = {
|
|
258
|
+
id: "epub",
|
|
259
|
+
label: "EPUB",
|
|
260
|
+
mimeType: MIME.epub,
|
|
261
|
+
extensions: [".epub"],
|
|
262
|
+
async exportDoc(input, options) {
|
|
263
|
+
const { markdownDocToEpub } = await import("./epub/index.js");
|
|
264
|
+
const images = await collectContainerImages(input.container);
|
|
265
|
+
const buf = await markdownDocToEpub(await markdownOf(input), {
|
|
266
|
+
title: options.title ?? input.baseName,
|
|
267
|
+
themeId: resolveThemeId(input, options),
|
|
268
|
+
images
|
|
269
|
+
});
|
|
270
|
+
return ok(await toBytes(buf), MIME.epub);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
const dbk = {
|
|
274
|
+
id: "dbk",
|
|
275
|
+
label: "Squisq container (DBK)",
|
|
276
|
+
mimeType: MIME.zip,
|
|
277
|
+
extensions: [".dbk", ".zip"],
|
|
278
|
+
async importContainer(data) {
|
|
279
|
+
const { zipToContainer } = await import("./container/index.js");
|
|
280
|
+
return zipToContainer(data);
|
|
281
|
+
},
|
|
282
|
+
async exportDoc(input) {
|
|
283
|
+
const { containerToZip } = await import("./container/index.js");
|
|
284
|
+
const blob = await containerToZip(input.container);
|
|
285
|
+
return ok(await toBytes(blob), MIME.zip);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
return [md, docx, pdf, pptx, xlsx, csv, html, htmlzip, epub, dbk];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/registry/registry.ts
|
|
292
|
+
function normalizeExt(ext) {
|
|
293
|
+
return ext.replace(/^\.+/, "").toLowerCase();
|
|
294
|
+
}
|
|
295
|
+
function createRegistry() {
|
|
296
|
+
const byId = /* @__PURE__ */ new Map();
|
|
297
|
+
return {
|
|
298
|
+
register(def) {
|
|
299
|
+
byId.set(def.id, def);
|
|
300
|
+
},
|
|
301
|
+
get(id) {
|
|
302
|
+
return byId.get(id);
|
|
303
|
+
},
|
|
304
|
+
byExtension(ext) {
|
|
305
|
+
const target = normalizeExt(ext);
|
|
306
|
+
if (!target) return void 0;
|
|
307
|
+
for (const def of byId.values()) {
|
|
308
|
+
if (def.extensions.some((e) => normalizeExt(e) === target)) return def;
|
|
309
|
+
}
|
|
310
|
+
return void 0;
|
|
311
|
+
},
|
|
312
|
+
list() {
|
|
313
|
+
return [...byId.values()];
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function defaultRegistry() {
|
|
318
|
+
const registry = createRegistry();
|
|
319
|
+
for (const def of defaultFormats()) {
|
|
320
|
+
registry.register(def);
|
|
321
|
+
}
|
|
322
|
+
return registry;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/registry/convert.ts
|
|
326
|
+
function hasPrefix(bytes, prefix) {
|
|
327
|
+
if (bytes.length < prefix.length) return false;
|
|
328
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
329
|
+
if (bytes[i] !== prefix[i]) return false;
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
var ZIP_MAGIC = [80, 75, 3, 4];
|
|
334
|
+
var PDF_MAGIC = [37, 80, 68, 70];
|
|
335
|
+
async function sniffZip(bytes) {
|
|
336
|
+
const JSZip = (await import("jszip")).default;
|
|
337
|
+
const zip = await JSZip.loadAsync(bytes).catch(() => {
|
|
338
|
+
throw new ConversionError("invalid-input", "Input is not a readable ZIP archive.");
|
|
339
|
+
});
|
|
340
|
+
const contentTypes = zip.file("[Content_Types].xml");
|
|
341
|
+
if (!contentTypes) return "dbk";
|
|
342
|
+
const xml = await contentTypes.async("string");
|
|
343
|
+
if (xml.includes("wordprocessingml")) return "docx";
|
|
344
|
+
if (xml.includes("presentationml")) return "pptx";
|
|
345
|
+
if (xml.includes("spreadsheetml")) return "xlsx";
|
|
346
|
+
return "dbk";
|
|
347
|
+
}
|
|
348
|
+
async function detectByteFormat(bytes, filename, options, registry) {
|
|
349
|
+
if (options.from) return options.from;
|
|
350
|
+
const ext = filename ? extractExt(filename) : void 0;
|
|
351
|
+
if (ext) {
|
|
352
|
+
const byExt = registry.byExtension(ext);
|
|
353
|
+
if (byExt) return byExt.id;
|
|
354
|
+
}
|
|
355
|
+
if (hasPrefix(bytes, PDF_MAGIC)) return "pdf";
|
|
356
|
+
if (hasPrefix(bytes, ZIP_MAGIC)) return sniffZip(bytes);
|
|
357
|
+
return "md";
|
|
358
|
+
}
|
|
359
|
+
function extractExt(filename) {
|
|
360
|
+
const base = filename.split("/").pop() ?? filename;
|
|
361
|
+
const dot = base.lastIndexOf(".");
|
|
362
|
+
if (dot <= 0) return "";
|
|
363
|
+
return base.slice(dot + 1).toLowerCase();
|
|
364
|
+
}
|
|
365
|
+
function baseNameOf(filename) {
|
|
366
|
+
const base = filename.split("/").pop() ?? filename;
|
|
367
|
+
const dot = base.lastIndexOf(".");
|
|
368
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
369
|
+
}
|
|
370
|
+
async function normalizeBytes(source, options, registry) {
|
|
371
|
+
const bytes = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
372
|
+
const buffer = bytes.buffer.slice(
|
|
373
|
+
bytes.byteOffset,
|
|
374
|
+
bytes.byteOffset + bytes.byteLength
|
|
375
|
+
);
|
|
376
|
+
const fromId = await detectByteFormat(bytes, source.filename, options, registry);
|
|
377
|
+
const fromDef = registry.get(fromId);
|
|
378
|
+
if (!fromDef) {
|
|
379
|
+
throw new ConversionError("unknown-format", `Unknown source format "${fromId}".`, {
|
|
380
|
+
format: fromId
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (!fromDef.importContainer && !fromDef.importDoc) {
|
|
384
|
+
throw new ConversionError(
|
|
385
|
+
"unsupported-input",
|
|
386
|
+
`Format "${fromDef.label}" cannot be used as a conversion source.`,
|
|
387
|
+
{
|
|
388
|
+
format: fromId,
|
|
389
|
+
hint: "This format is export-only."
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const { markdownToDoc } = await import("@bendyline/squisq/doc");
|
|
394
|
+
const { parseMarkdown, stringifyMarkdown } = await import("@bendyline/squisq/markdown");
|
|
395
|
+
const warnings = [];
|
|
396
|
+
let markdownDoc;
|
|
397
|
+
let container;
|
|
398
|
+
if (fromDef.importContainer) {
|
|
399
|
+
container = await fromDef.importContainer(buffer, options);
|
|
400
|
+
if (fromDef.id === "pdf" && typeof document === "undefined") {
|
|
401
|
+
warnings.push(
|
|
402
|
+
"PDF embedded images were skipped \u2014 image decoding requires a browser canvas (running under Node)."
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const text = await container.readDocument();
|
|
406
|
+
markdownDoc = text ? parseMarkdown(text) : { type: "document", children: [] };
|
|
407
|
+
} else {
|
|
408
|
+
markdownDoc = await fromDef.importDoc(buffer, options);
|
|
409
|
+
const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
|
|
410
|
+
const mem = new MemoryContentContainer();
|
|
411
|
+
await mem.writeDocument(stringifyMarkdown(markdownDoc));
|
|
412
|
+
container = mem;
|
|
413
|
+
}
|
|
414
|
+
const doc = markdownToDoc(markdownDoc, { autoTemplates: options.autoTemplates });
|
|
415
|
+
const baseName = source.filename ? baseNameOf(source.filename) : "document";
|
|
416
|
+
return {
|
|
417
|
+
input: { doc, markdownDoc, container, baseName },
|
|
418
|
+
warnings
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
async function normalizeMarkdown(source, options) {
|
|
422
|
+
const { markdownToDoc } = await import("@bendyline/squisq/doc");
|
|
423
|
+
const { parseMarkdown } = await import("@bendyline/squisq/markdown");
|
|
424
|
+
const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
|
|
425
|
+
const markdownDoc = typeof source.markdown === "string" ? parseMarkdown(source.markdown) : source.markdown;
|
|
426
|
+
let container = source.container;
|
|
427
|
+
if (!container) {
|
|
428
|
+
const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
|
|
429
|
+
const mem = new MemoryContentContainer();
|
|
430
|
+
await mem.writeDocument(stringifyMarkdown(markdownDoc));
|
|
431
|
+
container = mem;
|
|
432
|
+
}
|
|
433
|
+
const doc = markdownToDoc(markdownDoc, { autoTemplates: options.autoTemplates });
|
|
434
|
+
const baseName = source.baseName ?? "document";
|
|
435
|
+
return { input: { doc, markdownDoc, container, baseName }, warnings: [] };
|
|
436
|
+
}
|
|
437
|
+
async function normalizeDoc(source) {
|
|
438
|
+
let container = source.container;
|
|
439
|
+
if (!container) {
|
|
440
|
+
const { MemoryContentContainer } = await import("@bendyline/squisq/storage");
|
|
441
|
+
const { docToMarkdown } = await import("@bendyline/squisq/doc");
|
|
442
|
+
const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
|
|
443
|
+
const mem = new MemoryContentContainer();
|
|
444
|
+
await mem.writeDocument(stringifyMarkdown(docToMarkdown(source.doc)));
|
|
445
|
+
container = mem;
|
|
446
|
+
}
|
|
447
|
+
const baseName = source.baseName ?? "document";
|
|
448
|
+
return { input: { doc: source.doc, container, baseName }, warnings: [] };
|
|
449
|
+
}
|
|
450
|
+
async function normalize(source, options, registry) {
|
|
451
|
+
switch (source.kind) {
|
|
452
|
+
case "bytes":
|
|
453
|
+
return normalizeBytes(source, options, registry);
|
|
454
|
+
case "markdown":
|
|
455
|
+
return normalizeMarkdown(source, options);
|
|
456
|
+
case "doc":
|
|
457
|
+
return normalizeDoc(source);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async function applyTransformStyle(input, transformStyle, themeId) {
|
|
461
|
+
const { applyTransform, extractDocImages } = await import("@bendyline/squisq/transform");
|
|
462
|
+
const { docToMarkdown, markdownToDoc } = await import("@bendyline/squisq/doc");
|
|
463
|
+
const images = extractDocImages(input.doc.blocks);
|
|
464
|
+
const result = applyTransform(input.doc, transformStyle, { themeId, images });
|
|
465
|
+
input.doc = result.doc;
|
|
466
|
+
const markdownDoc = docToMarkdown(result.doc);
|
|
467
|
+
input.markdownDoc = markdownDoc;
|
|
468
|
+
const roundTripped = markdownToDoc(markdownDoc, { autoTemplates: false });
|
|
469
|
+
if (roundTripped.blocks.length < result.doc.blocks.length) {
|
|
470
|
+
return [
|
|
471
|
+
"Transform produced blocks that don\u2019t round-trip to markdown; export may be incomplete."
|
|
472
|
+
];
|
|
473
|
+
}
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
async function convert(source, to, options = {}) {
|
|
477
|
+
const registry = options.registry ?? defaultRegistry();
|
|
478
|
+
const target = registry.get(to);
|
|
479
|
+
if (!target) {
|
|
480
|
+
throw new ConversionError("unknown-format", `Unknown target format "${to}".`, { format: to });
|
|
481
|
+
}
|
|
482
|
+
if (!target.exportDoc) {
|
|
483
|
+
throw new ConversionError(
|
|
484
|
+
"unsupported-output",
|
|
485
|
+
`Format "${target.label}" does not support export.`,
|
|
486
|
+
{ format: to, hint: "This format is import-only." }
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const { input, warnings } = await normalize(source, options, registry);
|
|
490
|
+
if (options.themeId && !input.doc.themeId) {
|
|
491
|
+
input.doc.themeId = options.themeId;
|
|
492
|
+
}
|
|
493
|
+
if (options.transformStyle) {
|
|
494
|
+
warnings.push(
|
|
495
|
+
...await applyTransformStyle(
|
|
496
|
+
input,
|
|
497
|
+
options.transformStyle,
|
|
498
|
+
options.themeId ?? input.doc.themeId
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
let result;
|
|
503
|
+
try {
|
|
504
|
+
result = await target.exportDoc(input, options);
|
|
505
|
+
} catch (err) {
|
|
506
|
+
if (err instanceof ConversionError) throw err;
|
|
507
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
508
|
+
throw new ConversionError("conversion-failed", message, { format: to, cause: err });
|
|
509
|
+
}
|
|
510
|
+
const ext = target.extensions[0]?.replace(/^\.+/, "") ?? to;
|
|
511
|
+
return {
|
|
512
|
+
...result,
|
|
513
|
+
suggestedFilename: `${input.baseName}.${ext}`,
|
|
514
|
+
warnings: [...warnings, ...result.warnings]
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export {
|
|
519
|
+
BUILTIN_FORMAT_IDS,
|
|
520
|
+
ConversionError,
|
|
521
|
+
defaultFormats,
|
|
522
|
+
createRegistry,
|
|
523
|
+
defaultRegistry,
|
|
524
|
+
convert
|
|
525
|
+
};
|
|
526
|
+
//# sourceMappingURL=chunk-RTS5XBZ7.js.map
|