word-to-markdown 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -11
- package/build/cli.js +49 -22
- package/build/cli.js.map +1 -1
- package/build/main.d.ts +88 -0
- package/build/main.js +520 -376
- package/build/main.js.map +1 -1
- package/package.json +41 -25
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/word-to-markdown)
|
|
4
4
|
[](https://www.npmjs.com/package/word-to-markdown)
|
|
5
5
|
[](https://github.com/benbalter/word-to-markdown-js/actions/workflows/ci.yml)
|
|
6
|
-
[](https://github.com/benbalter/word-to-markdown-js/blob/main/LICENSE)
|
|
7
7
|
|
|
8
8
|
Convert Word documents to beautiful Markdown. Via command line, as a Node library, or in your browser. An even better version of the original [`word-to-markdown`](https://github.com/benbalter/word-to-markdown).
|
|
9
9
|
|
|
@@ -23,7 +23,7 @@ npx word-to-markdown input.docx > output.md
|
|
|
23
23
|
- Nested lists
|
|
24
24
|
- Tables
|
|
25
25
|
- Links
|
|
26
|
-
- Footnotes and endnotes
|
|
26
|
+
- Footnotes and endnotes — converted to GFM/Pandoc `[^1]` footnotes
|
|
27
27
|
- Images — embedded inline as base64 data URIs, or extracted to files
|
|
28
28
|
|
|
29
29
|
### Notes and limitations
|
|
@@ -41,6 +41,14 @@ npx word-to-markdown input.docx > output.md
|
|
|
41
41
|
- **Underline** is dropped by default (Mammoth's default, since underlines are
|
|
42
42
|
easily confused with links). Pass `{ underline: 'preserve' }` (library) or
|
|
43
43
|
`--underline` (CLI) to keep it as an inline `<u>` tag.
|
|
44
|
+
- **Footnotes and endnotes** become standard GFM/Pandoc footnotes — a `[^1]`
|
|
45
|
+
reference in the body and a `[^1]: …` definition at the end — which render on
|
|
46
|
+
GitHub and in the web preview. Pass `{ footnotes: 'preserve' }` (library) or
|
|
47
|
+
`--preserve-footnotes` (CLI) to instead keep Mammoth's raw `<sup>` links and
|
|
48
|
+
numbered note list (useful for CommonMark targets that lack footnote support).
|
|
49
|
+
A note whose body spans multiple paragraphs (a rare Word construct) is left in
|
|
50
|
+
Mammoth's raw form rather than converted, so its reference and body stay
|
|
51
|
+
linked.
|
|
44
52
|
- **Comments, text boxes, and equations are not converted** — Mammoth drops
|
|
45
53
|
them during the `.docx` → HTML step. When content is dropped this way,
|
|
46
54
|
`convertWithWarnings` surfaces a warning.
|
|
@@ -81,16 +89,23 @@ The converted Markdown is written to **stdout** and any document warnings (encry
|
|
|
81
89
|
|
|
82
90
|
Options:
|
|
83
91
|
|
|
92
|
+
- `-o, --output <file>` — write the Markdown to `<file>` instead of stdout
|
|
93
|
+
(warnings still go to stderr).
|
|
84
94
|
- `--bullet-lists` — convert numbered lists to bullets instead of keeping `1./2./3.`.
|
|
85
95
|
- `--underline` — preserve underlined text as inline `<u>` tags (dropped by default).
|
|
86
96
|
- `--strip-images` — remove images instead of embedding them as base64 data URIs.
|
|
87
97
|
- `--image-dir <dir>` — extract images to `<dir>` and link them relatively, instead
|
|
88
98
|
of embedding base64. Links resolve relative to where you save the Markdown, e.g.
|
|
89
|
-
`w2m --image-dir images report.docx > report.md
|
|
99
|
+
`w2m --image-dir images report.docx > report.md`, or with `-o`,
|
|
100
|
+
`w2m -o out/report.md --image-dir images report.docx` (images land in
|
|
101
|
+
`out/images/`).
|
|
102
|
+
- `--preserve-footnotes` — keep Word footnotes as raw `<sup>` links and a
|
|
103
|
+
numbered note list instead of GFM `[^1]` footnotes.
|
|
104
|
+
- `-V, --version` — print the version.
|
|
90
105
|
|
|
91
106
|
## Use as a library
|
|
92
107
|
|
|
93
|
-
Published to npm as [`word-to-markdown`](https://www.npmjs.com/package/word-to-markdown). It ships as an ES module and requires Node 22.13 or later.
|
|
108
|
+
Published to npm as [`word-to-markdown`](https://www.npmjs.com/package/word-to-markdown). It ships as an ES module with TypeScript declarations and requires Node 22.13 or later.
|
|
94
109
|
|
|
95
110
|
```console
|
|
96
111
|
npm install word-to-markdown
|
|
@@ -108,7 +123,7 @@ const { markdown, warnings } = await convertWithWarnings(
|
|
|
108
123
|
);
|
|
109
124
|
```
|
|
110
125
|
|
|
111
|
-
Both functions accept either a file-path string (
|
|
126
|
+
Both functions accept either a file-path string (Node) or an `ArrayBuffer` (Node or the browser):
|
|
112
127
|
|
|
113
128
|
```js
|
|
114
129
|
const { markdown } = await convertWithWarnings(arrayBuffer);
|
|
@@ -119,12 +134,13 @@ const { markdown } = await convertWithWarnings(arrayBuffer);
|
|
|
119
134
|
- **`convert(input, options?): Promise<string>`** — resolves to the Markdown.
|
|
120
135
|
- **`convertWithWarnings(input, options?): Promise<{ markdown: string; warnings: string[]; images? }>`** — also returns human-readable warnings for encrypted, protected, or sensitivity-labeled documents, and (in `extract` mode) the extracted images.
|
|
121
136
|
|
|
122
|
-
`input` is a file-path `string` (Node) or an `ArrayBuffer` (
|
|
137
|
+
`input` is a file-path `string` (Node) or an `ArrayBuffer`. `options` (type `ConvertOptions`) is optional:
|
|
123
138
|
|
|
124
139
|
- **`images`** — `'inline'` (default) embeds images as base64 data URIs; `'strip'` removes them; `'extract'` replaces each with a relative `` link and returns the bytes on `ConvertResult.images` (use `convertWithWarnings` to retrieve them).
|
|
125
140
|
- **`imageDir`** — link/path prefix for extracted images (default `'images'`); only applies with `images: 'extract'`.
|
|
126
141
|
- **`numberedLists`** — `'ordered'` (default) keeps `1./2./3.`; `'bullets'` converts numbered lists to bullets.
|
|
127
142
|
- **`underline`** — `'ignore'` (default) drops underlines; `'preserve'` keeps them as inline `<u>` tags.
|
|
143
|
+
- **`footnotes`** — `'gfm'` (default) converts footnotes and endnotes to `[^1]` references and definitions; `'preserve'` keeps Mammoth's raw `<sup>` links and numbered note list.
|
|
128
144
|
- **`mammoth`** / **`turndown`** — escape hatches forwarded to [Mammoth](https://github.com/mwilliamson/mammoth.js/) and [Turndown](https://github.com/mixmark-io/turndown) respectively.
|
|
129
145
|
|
|
130
146
|
```js
|
|
@@ -137,7 +153,7 @@ const { markdown, images } = await convertWithWarnings('file.docx', {
|
|
|
137
153
|
|
|
138
154
|
### Error handling
|
|
139
155
|
|
|
140
|
-
Conversion throws typed errors so you can respond to each failure precisely
|
|
156
|
+
Conversion throws typed errors so you can respond to each failure precisely. All of them extend `WordToMarkdownError`, so `error instanceof WordToMarkdownError` catches any of them.
|
|
141
157
|
|
|
142
158
|
```js
|
|
143
159
|
import convert, {
|
|
@@ -152,13 +168,13 @@ try {
|
|
|
152
168
|
const markdown = await convert('path/to/your/file.docx');
|
|
153
169
|
} catch (error) {
|
|
154
170
|
if (error instanceof UnsupportedFileError) {
|
|
155
|
-
//
|
|
171
|
+
// a .doc or password-protected file — only unprotected .docx is supported
|
|
156
172
|
} else if (error instanceof FileNotFoundError) {
|
|
157
|
-
// the path doesn't exist
|
|
173
|
+
// the path doesn't exist (or runs through something that isn't a directory)
|
|
158
174
|
} else if (error instanceof InvalidFileError) {
|
|
159
|
-
// not a valid or parseable .docx
|
|
175
|
+
// not a valid or parseable .docx (or the path is a directory)
|
|
160
176
|
} else if (error instanceof FilePermissionError) {
|
|
161
|
-
// the file couldn't be read
|
|
177
|
+
// the file couldn't be read, or is in a blocked system directory
|
|
162
178
|
} else if (error instanceof ConversionError) {
|
|
163
179
|
// something failed mid-conversion — see error.cause
|
|
164
180
|
}
|
|
@@ -190,6 +206,13 @@ To self-host the static site using Docker Compose:
|
|
|
190
206
|
3. Run `docker compose up -d`
|
|
191
207
|
4. Access at http://localhost:3000
|
|
192
208
|
|
|
209
|
+
This serves `dist/` with plain nginx, so it skips what the Cloudflare Worker and
|
|
210
|
+
`public/_headers` add in production: the `Accept-Language` redirect on `/`, the
|
|
211
|
+
`/api/event` counter (requests to it 404), the security headers, and long-lived
|
|
212
|
+
caching for `/_astro/*`. Conversion itself runs in the browser and works the
|
|
213
|
+
same. For a production-like local setup, run `npm run build` and then
|
|
214
|
+
`npx wrangler dev`.
|
|
215
|
+
|
|
193
216
|
## More context
|
|
194
217
|
|
|
195
218
|
See the README of [the original Word to Markdown](https://github.com/benbalter/word-to-markdown?tab=readme-ov-file#the-problem) for the project's motivation.
|
package/build/cli.js
CHANGED
|
@@ -1,39 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { __awaiter } from "tslib";
|
|
3
2
|
import { Command } from 'commander';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
4
|
import { mkdir, writeFile } from 'fs/promises';
|
|
5
|
-
import
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { convertWithWarnings } from './main.js';
|
|
7
|
+
// Read our own version from package.json. createRequire resolves relative to
|
|
8
|
+
// this module, so `../package.json` points at the package root both from the
|
|
9
|
+
// TypeScript source (src/) and the compiled CLI (build/) after publish. This
|
|
10
|
+
// avoids JSON import attributes, which node16 module resolution would require.
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const { version } = require('../package.json');
|
|
6
13
|
const program = new Command();
|
|
7
14
|
program.name('w2m');
|
|
8
15
|
program.description('Convert Word documents to beautiful Markdown');
|
|
16
|
+
program.version(version);
|
|
9
17
|
program
|
|
10
18
|
.command('convert', { isDefault: true })
|
|
11
19
|
.argument('<file>', 'The Word document to convert')
|
|
20
|
+
.option('-o, --output <file>', 'Write the Markdown to <file> instead of stdout. Warnings still print to ' +
|
|
21
|
+
'stderr.')
|
|
12
22
|
.option('--strip-images', 'Remove images instead of embedding them as base64 data URIs')
|
|
13
23
|
.option('--image-dir <dir>', 'Extract images to <dir> and link them relatively, instead of embedding ' +
|
|
14
24
|
'them as base64. Links resolve relative to where you save the Markdown.')
|
|
15
25
|
.option('--bullet-lists', 'Convert numbered lists to bullets rather than keeping them as 1./2./3.')
|
|
16
26
|
.option('--underline', 'Preserve underlined text as inline <u> tags (dropped by default)')
|
|
17
|
-
.
|
|
27
|
+
.option('--preserve-footnotes', 'Keep Word footnotes as raw <sup> links and a numbered note list instead ' +
|
|
28
|
+
'of converting them to GFM [^1] footnotes')
|
|
29
|
+
.option('--verbose', 'On failure, also print the underlying error and stack trace')
|
|
30
|
+
.action(async (file, options) => {
|
|
18
31
|
try {
|
|
19
32
|
// --image-dir (extract) takes precedence over --strip-images.
|
|
33
|
+
if (options.imageDir && options.stripImages) {
|
|
34
|
+
console.error('Ignoring --strip-images because --image-dir is set.');
|
|
35
|
+
}
|
|
20
36
|
const images = options.imageDir
|
|
21
37
|
? 'extract'
|
|
22
38
|
: options.stripImages
|
|
23
39
|
? 'strip'
|
|
24
40
|
: 'inline';
|
|
25
|
-
const result =
|
|
41
|
+
const result = await convertWithWarnings(file, {
|
|
26
42
|
images,
|
|
27
43
|
imageDir: options.imageDir,
|
|
28
44
|
numberedLists: options.bulletLists ? 'bullets' : 'ordered',
|
|
29
45
|
underline: options.underline ? 'preserve' : 'ignore',
|
|
46
|
+
footnotes: options.preserveFootnotes ? 'preserve' : 'gfm',
|
|
30
47
|
});
|
|
31
48
|
// Write extracted images to disk before emitting the Markdown that links
|
|
32
|
-
// them.
|
|
49
|
+
// them. The links are relative to the Markdown file, so resolve them
|
|
50
|
+
// against its directory (the working directory when writing to stdout).
|
|
33
51
|
if (result.images && result.images.length > 0) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
52
|
+
const markdownDir = options.output ? path.dirname(options.output) : '.';
|
|
53
|
+
const imageDir = path.resolve(markdownDir, options.imageDir);
|
|
54
|
+
await mkdir(imageDir, { recursive: true });
|
|
55
|
+
await Promise.all(result.images.map((image) => writeFile(path.resolve(markdownDir, image.path), image.bytes)));
|
|
56
|
+
console.error(`Wrote ${result.images.length} image(s) to ${path.relative('.', imageDir) || '.'}/`);
|
|
37
57
|
}
|
|
38
58
|
// Display warnings to stderr if any
|
|
39
59
|
if (result.warnings.length > 0) {
|
|
@@ -42,23 +62,30 @@ program
|
|
|
42
62
|
});
|
|
43
63
|
console.error(''); // Empty line for separation
|
|
44
64
|
}
|
|
45
|
-
//
|
|
46
|
-
|
|
65
|
+
// Write the Markdown to the requested file, or stdout by default.
|
|
66
|
+
if (options.output) {
|
|
67
|
+
await mkdir(path.dirname(options.output), { recursive: true });
|
|
68
|
+
// End with a newline, as stdout (console.log) does (markdownlint MD047).
|
|
69
|
+
await writeFile(options.output, `${result.markdown}\n`);
|
|
70
|
+
console.error(`Wrote Markdown to ${options.output}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(result.markdown);
|
|
74
|
+
}
|
|
47
75
|
}
|
|
48
76
|
catch (error) {
|
|
49
|
-
//
|
|
50
|
-
if (error instanceof UnsupportedFileError ||
|
|
51
|
-
error instanceof FileNotFoundError ||
|
|
52
|
-
error instanceof InvalidFileError ||
|
|
53
|
-
error instanceof FilePermissionError ||
|
|
54
|
-
error instanceof ConversionError) {
|
|
55
|
-
console.error(`Error: ${error.message}`);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
// Handle unexpected errors (including non-Error objects)
|
|
77
|
+
// Converter errors carry user-friendly messages; print anything else as-is
|
|
59
78
|
console.error('Error:', error instanceof Error ? error.message : String(error));
|
|
79
|
+
// The friendly message hides the root cause (e.g. a ConversionError
|
|
80
|
+
// wrapping a mammoth failure); --verbose shows it for bug reports.
|
|
81
|
+
if (options.verbose && error instanceof Error) {
|
|
82
|
+
console.error('');
|
|
83
|
+
console.error(error.stack);
|
|
84
|
+
if (error.cause)
|
|
85
|
+
console.error('Caused by:', error.cause);
|
|
86
|
+
}
|
|
60
87
|
process.exit(1);
|
|
61
88
|
}
|
|
62
|
-
})
|
|
63
|
-
program.
|
|
89
|
+
});
|
|
90
|
+
await program.parseAsync();
|
|
64
91
|
//# sourceMappingURL=cli.js.map
|
package/build/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { createRequire } from 'module';\nimport { mkdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport { convertWithWarnings } from './main.js';\n\n// Read our own version from package.json. createRequire resolves relative to\n// this module, so `../package.json` points at the package root both from the\n// TypeScript source (src/) and the compiled CLI (build/) after publish. This\n// avoids JSON import attributes, which node16 module resolution would require.\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json') as { version: string };\n\nconst program = new Command();\nprogram.name('w2m');\nprogram.description('Convert Word documents to beautiful Markdown');\nprogram.version(version);\nprogram\n .command('convert', { isDefault: true })\n .argument('<file>', 'The Word document to convert')\n .option(\n '-o, --output <file>',\n 'Write the Markdown to <file> instead of stdout. Warnings still print to ' +\n 'stderr.',\n )\n .option(\n '--strip-images',\n 'Remove images instead of embedding them as base64 data URIs',\n )\n .option(\n '--image-dir <dir>',\n 'Extract images to <dir> and link them relatively, instead of embedding ' +\n 'them as base64. Links resolve relative to where you save the Markdown.',\n )\n .option(\n '--bullet-lists',\n 'Convert numbered lists to bullets rather than keeping them as 1./2./3.',\n )\n .option(\n '--underline',\n 'Preserve underlined text as inline <u> tags (dropped by default)',\n )\n .option(\n '--preserve-footnotes',\n 'Keep Word footnotes as raw <sup> links and a numbered note list instead ' +\n 'of converting them to GFM [^1] footnotes',\n )\n .option(\n '--verbose',\n 'On failure, also print the underlying error and stack trace',\n )\n .action(async (file, options) => {\n try {\n // --image-dir (extract) takes precedence over --strip-images.\n if (options.imageDir && options.stripImages) {\n console.error('Ignoring --strip-images because --image-dir is set.');\n }\n const images = options.imageDir\n ? 'extract'\n : options.stripImages\n ? 'strip'\n : 'inline';\n const result = await convertWithWarnings(file, {\n images,\n imageDir: options.imageDir,\n numberedLists: options.bulletLists ? 'bullets' : 'ordered',\n underline: options.underline ? 'preserve' : 'ignore',\n footnotes: options.preserveFootnotes ? 'preserve' : 'gfm',\n });\n\n // Write extracted images to disk before emitting the Markdown that links\n // them. The links are relative to the Markdown file, so resolve them\n // against its directory (the working directory when writing to stdout).\n if (result.images && result.images.length > 0) {\n const markdownDir = options.output ? path.dirname(options.output) : '.';\n const imageDir = path.resolve(markdownDir, options.imageDir);\n await mkdir(imageDir, { recursive: true });\n await Promise.all(\n result.images.map((image) =>\n writeFile(path.resolve(markdownDir, image.path), image.bytes),\n ),\n );\n console.error(\n `Wrote ${result.images.length} image(s) to ${path.relative('.', imageDir) || '.'}/`,\n );\n }\n\n // Display warnings to stderr if any\n if (result.warnings.length > 0) {\n result.warnings.forEach((warning) => {\n console.error(warning);\n });\n console.error(''); // Empty line for separation\n }\n\n // Write the Markdown to the requested file, or stdout by default.\n if (options.output) {\n await mkdir(path.dirname(options.output), { recursive: true });\n // End with a newline, as stdout (console.log) does (markdownlint MD047).\n await writeFile(options.output, `${result.markdown}\\n`);\n console.error(`Wrote Markdown to ${options.output}`);\n } else {\n console.log(result.markdown);\n }\n } catch (error) {\n // Converter errors carry user-friendly messages; print anything else as-is\n console.error(\n 'Error:',\n error instanceof Error ? error.message : String(error),\n );\n // The friendly message hides the root cause (e.g. a ConversionError\n // wrapping a mammoth failure); --verbose shows it for bug reports.\n if (options.verbose && error instanceof Error) {\n console.error('');\n console.error(error.stack);\n if (error.cause) console.error('Caused by:', error.cause);\n }\n process.exit(1);\n }\n });\n\nawait program.parseAsync();\n"]}
|
package/build/main.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export interface ConvertOptions {
|
|
2
|
+
mammoth?: object;
|
|
3
|
+
turndown?: object;
|
|
4
|
+
/**
|
|
5
|
+
* How to handle images. `'inline'` (default) keeps Mammoth's base64 data
|
|
6
|
+
* URIs; `'strip'` removes images entirely (useful to avoid multi-MB output
|
|
7
|
+
* from image-heavy documents); `'extract'` replaces each image with a
|
|
8
|
+
* relative `` link and returns the image bytes on
|
|
9
|
+
* `ConvertResult.images` (use `convertWithWarnings` to retrieve them).
|
|
10
|
+
*/
|
|
11
|
+
images?: 'inline' | 'strip' | 'extract';
|
|
12
|
+
/**
|
|
13
|
+
* Directory prefix used for extracted image links and paths (default
|
|
14
|
+
* `'images'`). Only applies when `images` is `'extract'`. The same value is
|
|
15
|
+
* used for the Markdown link (``) and the returned
|
|
16
|
+
* `ExtractedImage.path`, so links and files always agree.
|
|
17
|
+
*/
|
|
18
|
+
imageDir?: string;
|
|
19
|
+
/**
|
|
20
|
+
* How to render Word's numbered lists. `'ordered'` (default) keeps them as
|
|
21
|
+
* `1.`/`2.`/… ordered lists; `'bullets'` converts them to bullet lists
|
|
22
|
+
* (matching the classic word-to-markdown behavior).
|
|
23
|
+
*/
|
|
24
|
+
numberedLists?: 'bullets' | 'ordered';
|
|
25
|
+
/**
|
|
26
|
+
* How to handle underlined text. `'ignore'` (default) drops the underline —
|
|
27
|
+
* Mammoth's default, since underlines are easily confused with links in HTML.
|
|
28
|
+
* `'preserve'` keeps it as an inline `<u>…</u>` tag (rendered by GitHub-flavored
|
|
29
|
+
* Markdown). Note that superscript and subscript are always preserved as
|
|
30
|
+
* `<sup>`/`<sub>` and need no option.
|
|
31
|
+
*/
|
|
32
|
+
underline?: 'ignore' | 'preserve';
|
|
33
|
+
/**
|
|
34
|
+
* How to render Word's footnotes and endnotes. `'gfm'` (default) rewrites
|
|
35
|
+
* Mammoth's superscript reference links plus trailing note list into standard
|
|
36
|
+
* GitHub-flavored/Pandoc footnote syntax (`[^1]` references and `[^1]:`
|
|
37
|
+
* definitions). `'preserve'` keeps Mammoth's raw `<sup>` links and numbered
|
|
38
|
+
* note list (useful for CommonMark targets that don't support `[^1]`).
|
|
39
|
+
*/
|
|
40
|
+
footnotes?: 'gfm' | 'preserve';
|
|
41
|
+
}
|
|
42
|
+
export interface ExtractedImage {
|
|
43
|
+
path: string;
|
|
44
|
+
contentType: string;
|
|
45
|
+
bytes: Uint8Array;
|
|
46
|
+
}
|
|
47
|
+
export interface ConvertResult {
|
|
48
|
+
markdown: string;
|
|
49
|
+
warnings: string[];
|
|
50
|
+
/** Present (possibly empty) when converting with `images: 'extract'`. */
|
|
51
|
+
images?: ExtractedImage[];
|
|
52
|
+
}
|
|
53
|
+
export interface DocumentProperties {
|
|
54
|
+
sensitivity?: string;
|
|
55
|
+
confidentiality?: string;
|
|
56
|
+
encryption?: boolean;
|
|
57
|
+
protection?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export declare class WordToMarkdownError extends Error {
|
|
60
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
61
|
+
}
|
|
62
|
+
export declare class UnsupportedFileError extends WordToMarkdownError {
|
|
63
|
+
constructor(message: string);
|
|
64
|
+
}
|
|
65
|
+
export declare class FileNotFoundError extends WordToMarkdownError {
|
|
66
|
+
constructor(filePath?: string);
|
|
67
|
+
}
|
|
68
|
+
export declare class InvalidFileError extends WordToMarkdownError {
|
|
69
|
+
constructor(filePath?: string, cause?: unknown);
|
|
70
|
+
}
|
|
71
|
+
export declare class FilePermissionError extends WordToMarkdownError {
|
|
72
|
+
constructor(filePath?: string);
|
|
73
|
+
}
|
|
74
|
+
export declare class ConversionError extends WordToMarkdownError {
|
|
75
|
+
constructor(message: string, originalError?: Error);
|
|
76
|
+
}
|
|
77
|
+
export declare function validateFileExtension(filePath: string): void;
|
|
78
|
+
export declare function htmlToMd(html: string, options?: object, keepTags?: string[], gfmFootnotes?: boolean): string;
|
|
79
|
+
export declare function extractDocumentProperties(input: string | ArrayBuffer): Promise<DocumentProperties>;
|
|
80
|
+
export declare function generateWarnings(properties: DocumentProperties): string[];
|
|
81
|
+
interface MammothMessage {
|
|
82
|
+
type: string;
|
|
83
|
+
message: string;
|
|
84
|
+
}
|
|
85
|
+
export declare function extractMammothWarnings(messages: readonly MammothMessage[]): string[];
|
|
86
|
+
export declare function convertWithWarnings(input: string | ArrayBuffer, options?: ConvertOptions): Promise<ConvertResult>;
|
|
87
|
+
export default function convert(input: string | ArrayBuffer, options?: ConvertOptions): Promise<string>;
|
|
88
|
+
export {};
|