@bendyline/squisq-formats 1.4.0 → 1.4.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 +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
package/dist/ooxml/index.js
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createPackage
|
|
3
|
-
} from "../chunk-QSE7EWE7.js";
|
|
4
|
-
import {
|
|
5
|
-
attrString,
|
|
6
|
-
escapeXml,
|
|
7
|
-
selfClosingElement,
|
|
8
|
-
textElement,
|
|
9
|
-
xmlDeclaration,
|
|
10
|
-
xmlElement
|
|
11
|
-
} from "../chunk-U4MRIFKL.js";
|
|
12
1
|
import {
|
|
13
2
|
CONTENT_TYPE_CORE_PROPERTIES,
|
|
14
3
|
CONTENT_TYPE_DOCX_DOCUMENT,
|
|
@@ -23,7 +12,9 @@ import {
|
|
|
23
12
|
CONTENT_TYPE_PPTX_SLIDE_MASTER,
|
|
24
13
|
CONTENT_TYPE_PPTX_THEME,
|
|
25
14
|
CONTENT_TYPE_RELATIONSHIPS,
|
|
15
|
+
CONTENT_TYPE_XLSX_STYLES,
|
|
26
16
|
CONTENT_TYPE_XLSX_WORKBOOK,
|
|
17
|
+
CONTENT_TYPE_XLSX_WORKSHEET,
|
|
27
18
|
NS_CONTENT_TYPES,
|
|
28
19
|
NS_CORE_PROPERTIES,
|
|
29
20
|
NS_DC,
|
|
@@ -52,12 +43,22 @@ import {
|
|
|
52
43
|
REL_SLIDE_MASTER,
|
|
53
44
|
REL_STYLES,
|
|
54
45
|
REL_THEME,
|
|
46
|
+
REL_WORKSHEET,
|
|
47
|
+
createPackage,
|
|
55
48
|
getCoreProperties,
|
|
56
49
|
getPartBinary,
|
|
57
50
|
getPartRelationships,
|
|
58
51
|
getPartXml,
|
|
59
52
|
openPackage
|
|
60
|
-
} from "../chunk-
|
|
53
|
+
} from "../chunk-SHLPSADL.js";
|
|
54
|
+
import {
|
|
55
|
+
attrString,
|
|
56
|
+
escapeXml,
|
|
57
|
+
selfClosingElement,
|
|
58
|
+
textElement,
|
|
59
|
+
xmlDeclaration,
|
|
60
|
+
xmlElement
|
|
61
|
+
} from "../chunk-U4MRIFKL.js";
|
|
61
62
|
export {
|
|
62
63
|
CONTENT_TYPE_CORE_PROPERTIES,
|
|
63
64
|
CONTENT_TYPE_DOCX_DOCUMENT,
|
|
@@ -72,7 +73,9 @@ export {
|
|
|
72
73
|
CONTENT_TYPE_PPTX_SLIDE_MASTER,
|
|
73
74
|
CONTENT_TYPE_PPTX_THEME,
|
|
74
75
|
CONTENT_TYPE_RELATIONSHIPS,
|
|
76
|
+
CONTENT_TYPE_XLSX_STYLES,
|
|
75
77
|
CONTENT_TYPE_XLSX_WORKBOOK,
|
|
78
|
+
CONTENT_TYPE_XLSX_WORKSHEET,
|
|
76
79
|
NS_CONTENT_TYPES,
|
|
77
80
|
NS_CORE_PROPERTIES,
|
|
78
81
|
NS_DC,
|
|
@@ -101,6 +104,7 @@ export {
|
|
|
101
104
|
REL_SLIDE_MASTER,
|
|
102
105
|
REL_STYLES,
|
|
103
106
|
REL_THEME,
|
|
107
|
+
REL_WORKSHEET,
|
|
104
108
|
attrString,
|
|
105
109
|
createPackage,
|
|
106
110
|
escapeXml,
|
package/dist/pdf/index.js
CHANGED
package/dist/pptx/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Doc } from '@bendyline/squisq/schemas';
|
|
2
2
|
import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
3
|
+
import { ContentContainer } from '@bendyline/squisq/storage';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* PPTX Export
|
|
@@ -87,10 +88,24 @@ declare function docToPptx(doc: Doc, options?: PptxExportOptions): Promise<Array
|
|
|
87
88
|
*/
|
|
88
89
|
|
|
89
90
|
interface PptxImportOptions {
|
|
90
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Whether to extract embedded slide images into the document as image nodes
|
|
93
|
+
* (referencing `images/imageN.ext`). When false, pictures are ignored so the
|
|
94
|
+
* markdown never carries dangling image references with no backing container.
|
|
95
|
+
* `pptxToContainer` forces this on. Default: false.
|
|
96
|
+
*/
|
|
91
97
|
extractImages?: boolean;
|
|
92
98
|
}
|
|
93
|
-
declare function pptxToMarkdownDoc(data: ArrayBuffer | Blob,
|
|
99
|
+
declare function pptxToMarkdownDoc(data: ArrayBuffer | Blob, options?: PptxImportOptions): Promise<MarkdownDocument>;
|
|
100
|
+
/**
|
|
101
|
+
* Convert a .pptx file to a ContentContainer with markdown + extracted images.
|
|
102
|
+
*
|
|
103
|
+
* The container holds the primary markdown document plus every embedded slide
|
|
104
|
+
* image under `images/` (e.g. `images/image1.png`). Image extraction is always
|
|
105
|
+
* forced on here so the markdown's image references resolve inside the
|
|
106
|
+
* container. Mirrors `docxToContainer`.
|
|
107
|
+
*/
|
|
108
|
+
declare function pptxToContainer(data: ArrayBuffer | Blob, options?: PptxImportOptions): Promise<ContentContainer>;
|
|
94
109
|
|
|
95
110
|
/**
|
|
96
111
|
* @bendyline/squisq-formats PPTX Module
|
|
@@ -114,4 +129,4 @@ declare function pptxToMarkdownDoc(data: ArrayBuffer | Blob, _options?: PptxImpo
|
|
|
114
129
|
*/
|
|
115
130
|
declare function pptxToDoc(data: ArrayBuffer | Blob, options?: PptxImportOptions): Promise<Doc>;
|
|
116
131
|
|
|
117
|
-
export { type PptxExportOptions, type PptxImportOptions, docToPptx, markdownDocToPptx, pptxToDoc, pptxToMarkdownDoc };
|
|
132
|
+
export { type PptxExportOptions, type PptxImportOptions, docToPptx, markdownDocToPptx, pptxToContainer, pptxToDoc, pptxToMarkdownDoc };
|
package/dist/pptx/index.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
docToPptx,
|
|
3
3
|
markdownDocToPptx,
|
|
4
|
+
pptxToContainer,
|
|
4
5
|
pptxToDoc,
|
|
5
6
|
pptxToMarkdownDoc
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-YESS7VY3.js";
|
|
7
8
|
import "../chunk-6M7Z25LA.js";
|
|
8
9
|
import "../chunk-PN52A5AA.js";
|
|
9
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-SHLPSADL.js";
|
|
10
11
|
import "../chunk-U4MRIFKL.js";
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
12
|
+
import "../chunk-SSUPBUF5.js";
|
|
13
|
+
import "../chunk-MJGRI6XR.js";
|
|
14
|
+
import "../chunk-DQAZR57U.js";
|
|
13
15
|
export {
|
|
14
16
|
docToPptx,
|
|
15
17
|
markdownDocToPptx,
|
|
18
|
+
pptxToContainer,
|
|
16
19
|
pptxToDoc,
|
|
17
20
|
pptxToMarkdownDoc
|
|
18
21
|
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Doc } from '@bendyline/squisq/schemas';
|
|
2
|
+
import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
3
|
+
import { ContentContainer } from '@bendyline/squisq/storage';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format registry — shared types.
|
|
7
|
+
*
|
|
8
|
+
* The registry is the programmatic heart of `convert()`: it maps a small set of
|
|
9
|
+
* format ids (`md`, `docx`, `pdf`, …) to `FormatDefinition`s, each of which
|
|
10
|
+
* knows how to import to / export from squisq's markdown + Doc model. Every
|
|
11
|
+
* converter module is loaded lazily via `import()` inside the definition
|
|
12
|
+
* methods, so pulling in the registry never eagerly bundles heavy converters.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** A format identifier (e.g. `'docx'`). Strings so hosts can register their own. */
|
|
16
|
+
type FormatId = string;
|
|
17
|
+
/** The built-in formats the default registry ships with. */
|
|
18
|
+
declare const BUILTIN_FORMAT_IDS: readonly ["md", "docx", "pdf", "pptx", "xlsx", "csv", "html", "htmlzip", "epub", "dbk"];
|
|
19
|
+
/** The bytes + metadata produced by a successful export. */
|
|
20
|
+
interface ConversionResult {
|
|
21
|
+
/** Encoded output bytes. */
|
|
22
|
+
bytes: Uint8Array;
|
|
23
|
+
/** MIME type of the output. */
|
|
24
|
+
mimeType: string;
|
|
25
|
+
/** A sensible download filename (`<baseName>.<ext>`). */
|
|
26
|
+
suggestedFilename: string;
|
|
27
|
+
/** Non-fatal notes accumulated during conversion (may be empty). */
|
|
28
|
+
warnings: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A source normalized into every shape an exporter might need. `doc` is always
|
|
32
|
+
* present; `markdownDoc` is present when the source was markdown-shaped (an
|
|
33
|
+
* exporter that wants markdown but finds none derives it from `doc`).
|
|
34
|
+
*/
|
|
35
|
+
interface NormalizedInput {
|
|
36
|
+
doc: Doc;
|
|
37
|
+
markdownDoc?: MarkdownDocument;
|
|
38
|
+
container: ContentContainer;
|
|
39
|
+
baseName: string;
|
|
40
|
+
}
|
|
41
|
+
/** Options threaded through `convert()` and into every format method. */
|
|
42
|
+
interface ConvertOptions {
|
|
43
|
+
/** Registry to resolve formats against. Defaults to `defaultRegistry()`. */
|
|
44
|
+
registry?: FormatRegistry;
|
|
45
|
+
/** Explicit source format id (skips extension/byte sniffing). */
|
|
46
|
+
from?: FormatId;
|
|
47
|
+
/** Theme id to apply to the exported document. */
|
|
48
|
+
themeId?: string;
|
|
49
|
+
/** Transform style id to apply before export. */
|
|
50
|
+
transformStyle?: string;
|
|
51
|
+
/** Content-aware auto-templating when deriving a Doc from markdown. */
|
|
52
|
+
autoTemplates?: boolean;
|
|
53
|
+
/** Title hint for exporters that support one (epub, html). */
|
|
54
|
+
title?: string;
|
|
55
|
+
/** Lazily resolve the standalone player IIFE bundle (required for HTML export). */
|
|
56
|
+
resolvePlayerScript?: () => Promise<string>;
|
|
57
|
+
/** Per-format escape hatch for extra options. */
|
|
58
|
+
formatOptions?: Record<FormatId, Record<string, unknown>>;
|
|
59
|
+
}
|
|
60
|
+
/** Describes how a single format imports to / exports from the squisq model. */
|
|
61
|
+
interface FormatDefinition {
|
|
62
|
+
id: FormatId;
|
|
63
|
+
label: string;
|
|
64
|
+
mimeType: string;
|
|
65
|
+
extensions: readonly string[];
|
|
66
|
+
/** Import raw bytes to a MarkdownDocument. */
|
|
67
|
+
importDoc?(data: ArrayBuffer, options: ConvertOptions): Promise<MarkdownDocument>;
|
|
68
|
+
/** Import raw bytes to a ContentContainer (markdown + extracted media). */
|
|
69
|
+
importContainer?(data: ArrayBuffer, options: ConvertOptions): Promise<ContentContainer>;
|
|
70
|
+
/** Export a normalized input to bytes. */
|
|
71
|
+
exportDoc?(input: NormalizedInput, options: ConvertOptions): Promise<ConversionResult>;
|
|
72
|
+
}
|
|
73
|
+
/** A mutable collection of format definitions keyed by id. */
|
|
74
|
+
interface FormatRegistry {
|
|
75
|
+
register(def: FormatDefinition): void;
|
|
76
|
+
get(id: FormatId): FormatDefinition | undefined;
|
|
77
|
+
byExtension(ext: string): FormatDefinition | undefined;
|
|
78
|
+
list(): FormatDefinition[];
|
|
79
|
+
}
|
|
80
|
+
/** The three shapes `convert()` accepts as a source. */
|
|
81
|
+
type ConvertSource = {
|
|
82
|
+
kind: 'bytes';
|
|
83
|
+
data: ArrayBuffer | Uint8Array;
|
|
84
|
+
filename?: string;
|
|
85
|
+
} | {
|
|
86
|
+
kind: 'markdown';
|
|
87
|
+
markdown: string | MarkdownDocument;
|
|
88
|
+
container?: ContentContainer;
|
|
89
|
+
baseName?: string;
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'doc';
|
|
92
|
+
doc: Doc;
|
|
93
|
+
container?: ContentContainer;
|
|
94
|
+
baseName?: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Structured error for the format registry / `convert()` pipeline.
|
|
99
|
+
*
|
|
100
|
+
* Every failure path in the registry throws a `ConversionError` with a stable
|
|
101
|
+
* machine-readable `code`, so callers can branch on the failure kind rather
|
|
102
|
+
* than string-matching messages.
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
/** Machine-readable failure kinds surfaced by the registry / `convert()`. */
|
|
106
|
+
type ConversionErrorCode = 'unknown-format' | 'unsupported-input' | 'unsupported-output' | 'invalid-input' | 'missing-dependency' | 'conversion-failed';
|
|
107
|
+
/** Options for constructing a {@link ConversionError}. */
|
|
108
|
+
interface ConversionErrorOptions {
|
|
109
|
+
/** The format id the error relates to, if any. */
|
|
110
|
+
format?: FormatId;
|
|
111
|
+
/** A human-oriented remediation hint. */
|
|
112
|
+
hint?: string;
|
|
113
|
+
/** The underlying error, preserved on `error.cause`. */
|
|
114
|
+
cause?: unknown;
|
|
115
|
+
}
|
|
116
|
+
/** An error raised by the format registry or `convert()`. */
|
|
117
|
+
declare class ConversionError extends Error {
|
|
118
|
+
readonly code: ConversionErrorCode;
|
|
119
|
+
readonly format?: FormatId;
|
|
120
|
+
readonly hint?: string;
|
|
121
|
+
constructor(code: ConversionErrorCode, message: string, opts?: ConversionErrorOptions);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Format registry implementation.
|
|
126
|
+
*
|
|
127
|
+
* `createRegistry()` returns an empty, mutable registry (last-write-wins by id);
|
|
128
|
+
* `defaultRegistry()` returns one preloaded with every built-in format.
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
/** Create an empty registry. Registering an existing id overwrites it. */
|
|
132
|
+
declare function createRegistry(): FormatRegistry;
|
|
133
|
+
/** Create a registry preloaded with all built-in formats. */
|
|
134
|
+
declare function defaultRegistry(): FormatRegistry;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Built-in format definitions.
|
|
138
|
+
*
|
|
139
|
+
* Each definition loads its converter module lazily via `import()` inside the
|
|
140
|
+
* method body, so importing the registry never eagerly bundles a heavy
|
|
141
|
+
* converter (pdf-lib, jszip, the OOXML writers, …). The capability of each
|
|
142
|
+
* format — which of import/export it supports — is expressed purely by which
|
|
143
|
+
* methods are present.
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
declare function defaultFormats(): FormatDefinition[];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Programmatic `convert()` — the format-registry front door.
|
|
150
|
+
*
|
|
151
|
+
* Normalizes any {@link ConvertSource} into a {@link NormalizedInput} (always
|
|
152
|
+
* yielding a `Doc`, keeping the `MarkdownDocument` when the source was
|
|
153
|
+
* markdown-shaped, and always carrying a `ContentContainer` for media), applies
|
|
154
|
+
* an optional transform, then hands off to the target format's exporter.
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Convert a source document to a target format.
|
|
159
|
+
*
|
|
160
|
+
* @param source - A bytes / markdown / doc source.
|
|
161
|
+
* @param to - The target format id (must be registered and support export).
|
|
162
|
+
* @param options - Conversion options (registry, theme, transform, …).
|
|
163
|
+
* @throws {@link ConversionError} on any failure, with a stable `code`.
|
|
164
|
+
*/
|
|
165
|
+
declare function convert(source: ConvertSource, to: FormatId, options?: ConvertOptions): Promise<ConversionResult>;
|
|
166
|
+
|
|
167
|
+
export { BUILTIN_FORMAT_IDS, ConversionError, type ConversionErrorCode, type ConversionErrorOptions, type ConversionResult, type ConvertOptions, type ConvertSource, type FormatDefinition, type FormatId, type FormatRegistry, type NormalizedInput, convert, createRegistry, defaultFormats, defaultRegistry };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BUILTIN_FORMAT_IDS,
|
|
3
|
+
ConversionError,
|
|
4
|
+
convert,
|
|
5
|
+
createRegistry,
|
|
6
|
+
defaultFormats,
|
|
7
|
+
defaultRegistry
|
|
8
|
+
} from "../chunk-RTS5XBZ7.js";
|
|
9
|
+
export {
|
|
10
|
+
BUILTIN_FORMAT_IDS,
|
|
11
|
+
ConversionError,
|
|
12
|
+
convert,
|
|
13
|
+
createRegistry,
|
|
14
|
+
defaultFormats,
|
|
15
|
+
defaultRegistry
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/xlsx/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
2
1
|
import { Doc } from '@bendyline/squisq/schemas';
|
|
2
|
+
import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* XLSX import — SpreadsheetML (.xlsx) → MarkdownDocument.
|
|
@@ -18,39 +18,69 @@ interface XlsxImportOptions {
|
|
|
18
18
|
declare function xlsxToMarkdownDoc(data: ArrayBuffer | Blob, options?: XlsxImportOptions): Promise<MarkdownDocument>;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* XLSX export — MarkdownDocument → SpreadsheetML (.xlsx).
|
|
22
|
+
*
|
|
23
|
+
* Tables-only fidelity (honestly documented): every `table` node in the
|
|
24
|
+
* markdown AST becomes one worksheet; all other content (headings, prose,
|
|
25
|
+
* lists, images, …) is dropped except that the nearest preceding heading is
|
|
26
|
+
* used to name the sheet. This mirrors the import side (`xlsxToMarkdownDoc`),
|
|
27
|
+
* which turns each worksheet grid back into a markdown table.
|
|
22
28
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
29
|
+
* Cells are emitted as inline strings (`t="inlineStr"`) so no sharedStrings
|
|
30
|
+
* part is needed, except values matching a plain-number pattern, which are
|
|
31
|
+
* emitted as numeric cells. The package is assembled with the shared ooxml/
|
|
32
|
+
* writer (auto-generates `[Content_Types].xml` + `_rels`), so only the
|
|
33
|
+
* SpreadsheetML-specific parts (workbook, worksheets, styles) are written here.
|
|
26
34
|
*
|
|
27
35
|
* @example
|
|
28
36
|
* ```ts
|
|
37
|
+
* import { parseMarkdown } from '@bendyline/squisq/markdown';
|
|
29
38
|
* import { markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';
|
|
39
|
+
*
|
|
40
|
+
* const md = parseMarkdown('# Metrics\n\n| A | B |\n| - | - |\n| 1 | 2 |');
|
|
41
|
+
* const buffer = await markdownDocToXlsx(md);
|
|
30
42
|
* ```
|
|
31
43
|
*/
|
|
32
44
|
|
|
33
45
|
/**
|
|
34
|
-
* Options for XLSX export
|
|
46
|
+
* Options for XLSX export.
|
|
35
47
|
*/
|
|
36
48
|
interface XlsxExportOptions {
|
|
37
|
-
/** Workbook title */
|
|
49
|
+
/** Workbook title (written to core properties). */
|
|
38
50
|
title?: string;
|
|
39
|
-
/** Workbook author */
|
|
51
|
+
/** Workbook author (written to core properties). */
|
|
40
52
|
author?: string;
|
|
53
|
+
/** Prefix used for auto-named sheets when no heading precedes a table. Default: "Sheet". */
|
|
54
|
+
sheetNamePrefix?: string;
|
|
41
55
|
}
|
|
42
56
|
/**
|
|
43
|
-
* Convert a MarkdownDocument to a .xlsx
|
|
57
|
+
* Convert a MarkdownDocument to a .xlsx file (tables-only fidelity).
|
|
44
58
|
*
|
|
45
|
-
*
|
|
59
|
+
* Each markdown `table` becomes one worksheet; a document with no tables
|
|
60
|
+
* yields a single empty sheet (a valid, openable file — never throws).
|
|
61
|
+
*/
|
|
62
|
+
declare function markdownDocToXlsx(doc: MarkdownDocument, options?: XlsxExportOptions): Promise<ArrayBuffer>;
|
|
63
|
+
/**
|
|
64
|
+
* Convert a squisq Doc to a .xlsx file (via the markdown table model).
|
|
46
65
|
*/
|
|
47
|
-
declare function
|
|
66
|
+
declare function docToXlsx(doc: Doc, options?: XlsxExportOptions): Promise<ArrayBuffer>;
|
|
67
|
+
|
|
48
68
|
/**
|
|
49
|
-
*
|
|
69
|
+
* @bendyline/squisq-formats XLSX Module
|
|
70
|
+
*
|
|
71
|
+
* Excel .xlsx support via SpreadsheetML and the shared ooxml/ infrastructure.
|
|
72
|
+
* Both directions are implemented: import (`xlsxToMarkdownDoc` / `xlsxToDoc`)
|
|
73
|
+
* turns each worksheet grid into a markdown table; export (`markdownDocToXlsx`
|
|
74
|
+
* / `docToXlsx`) turns each markdown table into a worksheet. Export is
|
|
75
|
+
* tables-only fidelity — non-table content is dropped (headings only survive
|
|
76
|
+
* as sheet names). See export.ts for details.
|
|
50
77
|
*
|
|
51
|
-
* @
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* import { xlsxToMarkdownDoc, markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';
|
|
81
|
+
* ```
|
|
52
82
|
*/
|
|
53
|
-
|
|
83
|
+
|
|
54
84
|
/**
|
|
55
85
|
* Convert a .xlsx file to a squisq Doc (via the markdown table model).
|
|
56
86
|
*/
|
package/dist/xlsx/index.js
CHANGED
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
markdownDocToXlsx,
|
|
4
4
|
xlsxToDoc,
|
|
5
5
|
xlsxToMarkdownDoc
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-EHLGMSTE.js";
|
|
7
|
+
import "../chunk-PN52A5AA.js";
|
|
8
|
+
import "../chunk-SHLPSADL.js";
|
|
9
|
+
import "../chunk-U4MRIFKL.js";
|
|
8
10
|
export {
|
|
9
11
|
docToXlsx,
|
|
10
12
|
markdownDocToXlsx,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-formats",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Document format converters — DOCX, PDF, OOXML import/export",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -81,6 +81,11 @@
|
|
|
81
81
|
"types": "./dist/container/index.d.ts",
|
|
82
82
|
"import": "./dist/container/index.js",
|
|
83
83
|
"default": "./dist/container/index.js"
|
|
84
|
+
},
|
|
85
|
+
"./registry": {
|
|
86
|
+
"types": "./dist/registry/index.d.ts",
|
|
87
|
+
"import": "./dist/registry/index.js",
|
|
88
|
+
"default": "./dist/registry/index.js"
|
|
84
89
|
}
|
|
85
90
|
},
|
|
86
91
|
"scripts": {
|
|
@@ -89,7 +94,7 @@
|
|
|
89
94
|
"typecheck": "tsc --noEmit"
|
|
90
95
|
},
|
|
91
96
|
"dependencies": {
|
|
92
|
-
"@bendyline/squisq": "1.5.
|
|
97
|
+
"@bendyline/squisq": "1.5.1",
|
|
93
98
|
"jszip": "3.10.1",
|
|
94
99
|
"pdf-lib": "1.17.1",
|
|
95
100
|
"pdfjs-dist": "4.10.38"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the programmatic convert() entry point: normalization across the
|
|
3
|
+
* three source shapes, byte sniffing, transform threading, round-trips, and
|
|
4
|
+
* suggested-filename correctness.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { parseMarkdown } from '@bendyline/squisq/markdown';
|
|
9
|
+
import { markdownToDoc } from '@bendyline/squisq/doc';
|
|
10
|
+
import { convert, ConversionError, defaultRegistry } from '../registry/index';
|
|
11
|
+
|
|
12
|
+
const SAMPLE_MD = `# Round Trip
|
|
13
|
+
|
|
14
|
+
This is a paragraph that should survive a docx round trip.
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
// ── unknown-format / unsupported-output ─────────────────────────────
|
|
18
|
+
|
|
19
|
+
describe('convert error paths', () => {
|
|
20
|
+
it('throws unknown-format for an unregistered target', async () => {
|
|
21
|
+
await expect(convert({ kind: 'markdown', markdown: '# hi' }, 'nope')).rejects.toMatchObject({
|
|
22
|
+
name: 'ConversionError',
|
|
23
|
+
code: 'unknown-format',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('throws unsupported-output when exporting to an import-only format', async () => {
|
|
28
|
+
// Register a definition with only importDoc, then export to it.
|
|
29
|
+
const registry = defaultRegistry();
|
|
30
|
+
registry.register({
|
|
31
|
+
id: 'importonly',
|
|
32
|
+
label: 'Import Only',
|
|
33
|
+
mimeType: 'application/x-import',
|
|
34
|
+
extensions: ['.imp'],
|
|
35
|
+
async importDoc() {
|
|
36
|
+
return parseMarkdown('# x');
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
await expect(
|
|
40
|
+
convert({ kind: 'markdown', markdown: '# hi' }, 'importonly', { registry }),
|
|
41
|
+
).rejects.toMatchObject({ name: 'ConversionError', code: 'unsupported-output' });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ── round trip: md → docx → md ──────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
describe('md → docx → md round trip', () => {
|
|
48
|
+
it('preserves paragraph text through a docx round trip', async () => {
|
|
49
|
+
const toDocx = await convert(
|
|
50
|
+
{ kind: 'markdown', markdown: SAMPLE_MD, baseName: 'doc' },
|
|
51
|
+
'docx',
|
|
52
|
+
);
|
|
53
|
+
expect(toDocx.mimeType).toContain('wordprocessingml');
|
|
54
|
+
expect(toDocx.bytes.byteLength).toBeGreaterThan(0);
|
|
55
|
+
|
|
56
|
+
// Feed the docx bytes back in with an explicit `from`.
|
|
57
|
+
const backToMd = await convert(
|
|
58
|
+
{ kind: 'bytes', data: toDocx.bytes, filename: 'doc.docx' },
|
|
59
|
+
'md',
|
|
60
|
+
{ from: 'docx' },
|
|
61
|
+
);
|
|
62
|
+
const text = new TextDecoder().decode(backToMd.bytes);
|
|
63
|
+
expect(text).toContain('survive a docx round trip');
|
|
64
|
+
expect(backToMd.suggestedFilename).toBe('doc.md');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── byte sniffing ───────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
describe('byte sniffing', () => {
|
|
71
|
+
it('sniffs a PDF by its %PDF magic', async () => {
|
|
72
|
+
// Stub the pdf importer so the test exercises the sniff, not pdfjs (which
|
|
73
|
+
// can't run under jsdom). Any %PDF-prefixed bytes must route to 'pdf'.
|
|
74
|
+
const registry = defaultRegistry();
|
|
75
|
+
registry.register({
|
|
76
|
+
...registry.get('pdf')!,
|
|
77
|
+
importContainer: undefined, // force the importDoc path (skip pdfjs)
|
|
78
|
+
async importDoc() {
|
|
79
|
+
return parseMarkdown('# sniffed as pdf');
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const pdfBytes = new TextEncoder().encode('%PDF-1.4\n%mock pdf body\n');
|
|
83
|
+
const back = await convert({ kind: 'bytes', data: pdfBytes }, 'md', { registry });
|
|
84
|
+
expect(new TextDecoder().decode(back.bytes)).toContain('sniffed as pdf');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('sniffs a squisq container (plain zip, no [Content_Types].xml) as dbk', async () => {
|
|
88
|
+
// A dbk is a ZIP with a markdown doc and no OOXML content-types part.
|
|
89
|
+
const dbk = await convert({ kind: 'markdown', markdown: '# In a container\n\nBody.' }, 'dbk');
|
|
90
|
+
expect(dbk.mimeType).toBe('application/zip');
|
|
91
|
+
|
|
92
|
+
const back = await convert({ kind: 'bytes', data: dbk.bytes }, 'md');
|
|
93
|
+
const text = new TextDecoder().decode(back.bytes);
|
|
94
|
+
expect(text).toContain('In a container');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('disambiguates a docx zip via its content-types part', async () => {
|
|
98
|
+
const docx = await convert({ kind: 'markdown', markdown: '# Word doc' }, 'docx');
|
|
99
|
+
// No filename, no `from` — must read [Content_Types].xml to pick docx.
|
|
100
|
+
const back = await convert({ kind: 'bytes', data: docx.bytes }, 'md');
|
|
101
|
+
const text = new TextDecoder().decode(back.bytes);
|
|
102
|
+
expect(text).toContain('Word doc');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ── transform threading ─────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
describe('transform threading', () => {
|
|
109
|
+
it('applies a transform style before handing off to the exporter', async () => {
|
|
110
|
+
// A capture format records the NormalizedInput it receives, letting us
|
|
111
|
+
// observe that the transform ran without depending on any converter.
|
|
112
|
+
let captured: import('../registry/index').NormalizedInput | undefined;
|
|
113
|
+
const registry = defaultRegistry();
|
|
114
|
+
registry.register({
|
|
115
|
+
id: 'capture',
|
|
116
|
+
label: 'Capture',
|
|
117
|
+
mimeType: 'application/x-capture',
|
|
118
|
+
extensions: ['.cap'],
|
|
119
|
+
async exportDoc(input) {
|
|
120
|
+
captured = input;
|
|
121
|
+
return {
|
|
122
|
+
bytes: new Uint8Array([1]),
|
|
123
|
+
mimeType: 'application/x-capture',
|
|
124
|
+
suggestedFilename: '',
|
|
125
|
+
warnings: [],
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const plainDoc = markdownToDoc(parseMarkdown(SAMPLE_MD));
|
|
131
|
+
await convert({ kind: 'markdown', markdown: SAMPLE_MD }, 'capture', {
|
|
132
|
+
registry,
|
|
133
|
+
transformStyle: 'magazine',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(captured).toBeDefined();
|
|
137
|
+
// The transform promotes/reshapes blocks, so the exporter sees a different
|
|
138
|
+
// Doc than a plain markdownToDoc of the same source.
|
|
139
|
+
expect(captured!.doc.blocks.length).not.toBe(plainDoc.blocks.length);
|
|
140
|
+
// markdownDoc is re-derived from the transformed Doc.
|
|
141
|
+
expect(captured!.markdownDoc).toBeDefined();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ── doc-kind source ─────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
describe('doc-kind source', () => {
|
|
148
|
+
it('exports a Doc source', async () => {
|
|
149
|
+
const doc = markdownToDoc(parseMarkdown('# From a Doc\n\nContent.'));
|
|
150
|
+
const result = await convert({ kind: 'doc', doc, baseName: 'fromdoc' }, 'md');
|
|
151
|
+
const text = new TextDecoder().decode(result.bytes);
|
|
152
|
+
expect(text).toContain('From a Doc');
|
|
153
|
+
expect(result.suggestedFilename).toBe('fromdoc.md');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ── suggested filename ──────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
describe('suggestedFilename', () => {
|
|
160
|
+
it('uses the primary extension per format', async () => {
|
|
161
|
+
const docx = await convert({ kind: 'markdown', markdown: '# t', baseName: 'report' }, 'docx');
|
|
162
|
+
expect(docx.suggestedFilename).toBe('report.docx');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('htmlzip suggests <base>.html.zip', async () => {
|
|
166
|
+
const zip = await convert(
|
|
167
|
+
{ kind: 'markdown', markdown: '# t', baseName: 'slides' },
|
|
168
|
+
'htmlzip',
|
|
169
|
+
{
|
|
170
|
+
resolvePlayerScript: async () => '/* stub */',
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
expect(zip.suggestedFilename).toBe('slides.html.zip');
|
|
174
|
+
expect(zip.mimeType).toBe('application/zip');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('defaults baseName to "document" when none is given', async () => {
|
|
178
|
+
const out = await convert({ kind: 'markdown', markdown: '# t' }, 'md');
|
|
179
|
+
expect(out.suggestedFilename).toBe('document.md');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// sanity: ConversionError is exported and usable
|
|
184
|
+
it('exports ConversionError', () => {
|
|
185
|
+
expect(new ConversionError('conversion-failed', 'x')).toBeInstanceOf(Error);
|
|
186
|
+
});
|