@ryangarber/markitdown-ts 0.1.5 → 0.2.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 +21 -10
- package/dist/cli.mjs +424 -329
- package/dist/index.cjs +788 -645
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -34
- package/dist/index.d.ts +19 -34
- package/dist/index.js +398 -316
- package/dist/index.js.map +1 -1
- package/dist/markitdown.1 +30 -0
- package/package.json +39 -21
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.ts.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { argument, commandLine, flag, message, object, option, optional, string, withDefault } from "@optique/core";
|
|
5
|
+
import { url } from "@optique/core/message";
|
|
6
|
+
import { defineProgram } from "@optique/core/program";
|
|
7
|
+
import { path, runSync } from "@optique/run";
|
|
5
8
|
import { parseHTML } from "linkedom";
|
|
9
|
+
import TurndownPluginGfm from "@truto/turndown-plugin-gfm";
|
|
10
|
+
import TurndownService from "turndown";
|
|
11
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
12
|
+
import { Mime } from "mime";
|
|
13
|
+
import otherTypes from "mime/types/other.js";
|
|
14
|
+
import standardTypes from "mime/types/standard.js";
|
|
6
15
|
import mammoth from "mammoth";
|
|
7
16
|
import { extractText, getDocumentProxy, getMeta } from "unpdf";
|
|
8
17
|
import { pptxToHtml } from "@jvmr/pptx-to-html";
|
|
9
18
|
import { DOMParser, Document, Element, Node } from "@xmldom/xmldom";
|
|
10
19
|
import readExcelFile from "read-excel-file/universal";
|
|
11
20
|
import { unzipSync } from "fflate";
|
|
12
|
-
|
|
13
|
-
var version = "0.
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
// package.json
|
|
22
|
+
var version = "0.2.0";
|
|
23
|
+
var bugs = { "url": "https://github.com/ryangarber/markitdown-ts/issues" };
|
|
24
|
+
var author = "Ryan Garber";
|
|
25
|
+
// src/utils/bytes.ts
|
|
16
26
|
async function toUint8Array(source) {
|
|
17
27
|
if (source instanceof Uint8Array) return source;
|
|
18
28
|
if (isBlob(source)) return new Uint8Array(await source.arrayBuffer());
|
|
@@ -28,12 +38,11 @@ function decodeText(source) {
|
|
|
28
38
|
function isBlob(value) {
|
|
29
39
|
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
30
40
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
var CustomTurnDown = class {
|
|
41
|
+
// src/utils/turndown.ts
|
|
42
|
+
var Turndown = class {
|
|
34
43
|
convertSoup(document) {
|
|
35
44
|
const service = new TurndownService({ headingStyle: "atx" });
|
|
36
|
-
service.use(
|
|
45
|
+
service.use(TurndownPluginGfm);
|
|
37
46
|
service.addRule("anchor tags", {
|
|
38
47
|
filter: ["a"],
|
|
39
48
|
replacement(content, node) {
|
|
@@ -68,48 +77,166 @@ var CustomTurnDown = class {
|
|
|
68
77
|
return ``;
|
|
69
78
|
}
|
|
70
79
|
});
|
|
71
|
-
return service.turndown(document);
|
|
80
|
+
return service.turndown(serializeNode(document));
|
|
72
81
|
}
|
|
73
82
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Turndown and its GFM plugin rely on DOM APIs (e.g. `HTMLTableElement.rows`, `Element.closest`)
|
|
85
|
+
* that linkedom does not implement, which silently drops content such as tables. Serializing
|
|
86
|
+
* lets Turndown re-parse the markup with its own spec-compliant DOM.
|
|
87
|
+
*/
|
|
88
|
+
function serializeNode(node) {
|
|
89
|
+
if (typeof node === "string") return node;
|
|
90
|
+
if (node.nodeType === node.DOCUMENT_NODE) return node.documentElement?.outerHTML ?? "";
|
|
91
|
+
if (node.nodeType === node.ELEMENT_NODE) return node.outerHTML;
|
|
92
|
+
if (node.nodeType === node.TEXT_NODE) return escapeHtml$1(node.textContent ?? "");
|
|
93
|
+
return [...node.childNodes].map(serializeNode).join("");
|
|
94
|
+
}
|
|
95
|
+
function escapeHtml$1(value) {
|
|
96
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
97
|
+
}
|
|
98
|
+
// src/utils/html.ts
|
|
99
|
+
function parseHtmlDocument(html, transform) {
|
|
100
|
+
const document = parseHTML(wrapFragment(html)).document;
|
|
101
|
+
return Object.fromEntries(Object.entries(transform(document)).map(([key, value]) => {
|
|
102
|
+
if (value.type === "node") return [key, (Array.isArray(value.value) ? value.value : [value.value]).map((node) => new Turndown().convertSoup(node).split(/\n+/).map((line) => line.trim()).filter(Boolean).join("\n\n")).join("\n\n")];
|
|
103
|
+
return [key, value.value];
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* linkedom only parses complete documents: given a fragment (e.g. mammoth output or an RSS
|
|
108
|
+
* description), it promotes the first element to `documentElement` and drops its siblings
|
|
109
|
+
* from the tree, so wrap fragments in a full document first.
|
|
110
|
+
*/
|
|
111
|
+
function wrapFragment(html) {
|
|
112
|
+
if (/<html[\s>]/i.test(html)) return html;
|
|
113
|
+
if (/<(head|body)[\s>]/i.test(html)) return `<html>${html}</html>`;
|
|
114
|
+
return `<html><head></head><body>${html}</body></html>`;
|
|
115
|
+
}
|
|
116
|
+
// src/utils/mime.ts
|
|
117
|
+
const mime = new Mime(standardTypes, otherTypes);
|
|
118
|
+
function extensionFromMime(contentType) {
|
|
119
|
+
if (!contentType) return null;
|
|
120
|
+
return extensionNormalized(mime.getExtension(contentType));
|
|
121
|
+
}
|
|
122
|
+
async function extensionFromBuffer(buffer) {
|
|
123
|
+
const type = await fileTypeFromBuffer(buffer);
|
|
124
|
+
if (!type) return null;
|
|
125
|
+
return extensionNormalized(type.ext);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Sniffs text formats that have no magic number. Returns null for binary content, and `txt`
|
|
129
|
+
* for text that is not recognized as anything more specific.
|
|
130
|
+
*/
|
|
131
|
+
function extensionFromText(buffer) {
|
|
132
|
+
let text;
|
|
133
|
+
try {
|
|
134
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (hasBinaryControlCharacters(text)) return null;
|
|
139
|
+
const start = text.trimStart().slice(0, 1024).toLowerCase();
|
|
140
|
+
if (start.startsWith("<")) {
|
|
141
|
+
if (/<rss[\s>]/.test(start)) return "rss";
|
|
142
|
+
if (/<feed[\s>]/.test(start)) return "atom";
|
|
143
|
+
if (/^<!doctype html|<(html|head|body)[\s>]/.test(start)) return "html";
|
|
144
|
+
if (start.startsWith("<?xml")) return "xml";
|
|
145
|
+
}
|
|
146
|
+
if (start.startsWith("{") && /"nbformat"\s*:/.test(text) && /"cells"\s*:/.test(text)) return "ipynb";
|
|
147
|
+
return "txt";
|
|
148
|
+
}
|
|
149
|
+
function hasBinaryControlCharacters(text) {
|
|
150
|
+
for (let index = 0; index < text.length; index++) {
|
|
151
|
+
const code = text.charCodeAt(index);
|
|
152
|
+
if (code < 32 && (code < 9 || code > 13) && code !== 27) return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
function extensionFromPathname(pathname) {
|
|
157
|
+
const filename = pathname.split("/").pop() ?? "";
|
|
158
|
+
const dot = filename.lastIndexOf(".");
|
|
159
|
+
return dot > 0 ? extensionNormalized(filename.slice(dot)) : null;
|
|
160
|
+
}
|
|
161
|
+
/** Reads the filename from a Content-Disposition header, preferring the RFC 5987 form. */
|
|
162
|
+
function filenameFromDisposition(disposition) {
|
|
163
|
+
if (!disposition) return null;
|
|
164
|
+
const extended = disposition.match(/filename\*\s*=\s*[\w-]*'[^']*'([^;]+)/i)?.[1]?.trim();
|
|
165
|
+
if (extended) try {
|
|
166
|
+
return decodeURIComponent(extended);
|
|
167
|
+
} catch {
|
|
168
|
+
return extended;
|
|
169
|
+
}
|
|
170
|
+
const plain = disposition.match(/filename\s*=\s*(?:"([^"]*)"|([^;]+))/i);
|
|
171
|
+
return (plain?.[1] ?? plain?.[2])?.trim() || null;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Lists candidate extensions for the content, most reliable first: explicit extensions, then
|
|
175
|
+
* names, then declared MIME types, then the content itself.
|
|
176
|
+
*/
|
|
177
|
+
async function detectExtensions(buffer, { extension, filename, urls = [], contentType } = {}) {
|
|
178
|
+
const candidates = [
|
|
179
|
+
extensionNormalized(extension),
|
|
180
|
+
filename ? extensionFromPathname(filename) : null,
|
|
181
|
+
...urls.map((url) => url ? extensionFromUrl(url) : null),
|
|
182
|
+
extensionFromMime(contentType),
|
|
183
|
+
await extensionFromBuffer(buffer) ?? extensionFromText(buffer)
|
|
184
|
+
];
|
|
185
|
+
return [...new Set(candidates.filter((candidate) => !!candidate))];
|
|
186
|
+
}
|
|
187
|
+
function extensionFromUrl(url) {
|
|
188
|
+
try {
|
|
189
|
+
return extensionFromPathname(decodeURIComponent(new URL(url).pathname));
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function extensionNormalized(extension, withDot = false) {
|
|
195
|
+
if (!extension) return "";
|
|
196
|
+
const normalized = extension.trim().toLowerCase().replace(/^[.-]+/, "");
|
|
197
|
+
return withDot ? `.${normalized}` : normalized;
|
|
198
|
+
}
|
|
199
|
+
function isTextExtension(extension) {
|
|
200
|
+
const type = mime.getType(extension);
|
|
201
|
+
if (!type) return false;
|
|
202
|
+
return type.startsWith("text/");
|
|
78
203
|
}
|
|
79
|
-
|
|
80
|
-
//#region src/converters/bingserp.ts
|
|
204
|
+
// src/converters/bingserp.ts
|
|
81
205
|
const BING_SEARCH_URL = /^https:\/\/www\.bing\.com\/search\?q=/i;
|
|
82
206
|
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
83
207
|
var BingSerpConverter = class {
|
|
84
208
|
async convert(source, options = {}) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (element.textContent) element.textContent += " ";
|
|
92
|
-
});
|
|
93
|
-
document.querySelectorAll(".algoSlug_icon").forEach((element) => {
|
|
94
|
-
element.remove();
|
|
95
|
-
});
|
|
96
|
-
const turndown = new CustomTurnDown();
|
|
97
|
-
const markdown = `## A Bing search for '${query}' found the following results:\n\n${[...document.querySelectorAll(".b_algo")].map((result) => {
|
|
98
|
-
result.querySelectorAll("a[href]").forEach((anchor) => {
|
|
99
|
-
const href = anchor.getAttribute("href");
|
|
100
|
-
if (!href) return;
|
|
101
|
-
try {
|
|
102
|
-
const encoded = new URL(href, url).searchParams.get("u");
|
|
103
|
-
if (encoded) anchor.setAttribute("href", decodeBingUrl(encoded));
|
|
104
|
-
} catch {}
|
|
209
|
+
if (options.extension !== "html" && options.extension !== "htm" || !BING_SEARCH_URL.test(options.url ?? "")) return null;
|
|
210
|
+
let query = new URL(options.url ?? "").searchParams.get("q") || null;
|
|
211
|
+
if (query) query = `Search results for "${query}"`;
|
|
212
|
+
return parseHtmlDocument(decodeText(source), (document) => {
|
|
213
|
+
document.querySelectorAll(".tptt").forEach((element) => {
|
|
214
|
+
if (element.textContent) element.textContent += " ";
|
|
105
215
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
216
|
+
document.querySelectorAll(".algoSlug_icon").forEach((element) => {
|
|
217
|
+
element.remove();
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
title: {
|
|
221
|
+
type: "metadata",
|
|
222
|
+
value: query || document.title || null
|
|
223
|
+
},
|
|
224
|
+
markdown: {
|
|
225
|
+
type: "node",
|
|
226
|
+
value: [...document.querySelectorAll(".b_algo")].map((result) => {
|
|
227
|
+
result.querySelectorAll("a[href]").forEach((anchor) => {
|
|
228
|
+
const href = anchor.getAttribute("href");
|
|
229
|
+
if (!href) return;
|
|
230
|
+
try {
|
|
231
|
+
const encoded = new URL(href, options.url ?? "").searchParams.get("u");
|
|
232
|
+
if (encoded) anchor.setAttribute("href", decodeBingUrl(encoded));
|
|
233
|
+
} catch {}
|
|
234
|
+
});
|
|
235
|
+
return result;
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
});
|
|
113
240
|
}
|
|
114
241
|
};
|
|
115
242
|
function decodeBingUrl(encoded) {
|
|
@@ -129,52 +256,46 @@ function decodeBingUrl(encoded) {
|
|
|
129
256
|
}
|
|
130
257
|
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
131
258
|
}
|
|
132
|
-
|
|
133
|
-
//#region src/converters/html.ts
|
|
259
|
+
// src/converters/html.ts
|
|
134
260
|
var HtmlConverter = class {
|
|
135
261
|
async convert(source, options) {
|
|
136
|
-
|
|
137
|
-
if (extension !== ".html" && extension !== ".htm") return null;
|
|
262
|
+
if (options.extension !== "html" && options.extension !== "htm") return null;
|
|
138
263
|
return this.convertHtml(decodeText(source));
|
|
139
264
|
}
|
|
140
265
|
async convertHtml(html) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
266
|
+
return parseHtmlDocument(html, (document) => {
|
|
267
|
+
const title = document.title || null;
|
|
268
|
+
document.querySelectorAll("script, style, title").forEach((element) => {
|
|
269
|
+
element.remove();
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
title: {
|
|
273
|
+
type: "metadata",
|
|
274
|
+
value: title
|
|
275
|
+
},
|
|
276
|
+
markdown: {
|
|
277
|
+
type: "node",
|
|
278
|
+
value: document
|
|
279
|
+
}
|
|
280
|
+
};
|
|
145
281
|
});
|
|
146
|
-
const markdown = new CustomTurnDown().convertSoup(document);
|
|
147
|
-
return {
|
|
148
|
-
title: title || null,
|
|
149
|
-
markdown,
|
|
150
|
-
text_content: markdown
|
|
151
|
-
};
|
|
152
282
|
}
|
|
153
283
|
};
|
|
154
|
-
|
|
155
|
-
//#region src/converters/docx.ts
|
|
284
|
+
// src/converters/docx.ts
|
|
156
285
|
var DocxConverter = class extends HtmlConverter {
|
|
157
286
|
async convert(source, options) {
|
|
158
|
-
if (options.
|
|
287
|
+
if (options.extension !== "docx") return null;
|
|
159
288
|
let mammothInput = { arrayBuffer: toArrayBuffer(source) };
|
|
160
289
|
if (typeof process !== "undefined" && process.versions !== null && process.versions.node !== null) mammothInput = { buffer: Buffer.from(source) };
|
|
161
|
-
const mammothOptions = {
|
|
162
|
-
styleMap: options.styleMap,
|
|
163
|
-
includeEmbeddedStyleMap: options.includeEmbeddedStyleMap,
|
|
164
|
-
includeDefaultStyleMap: options.includeDefaultStyleMap,
|
|
165
|
-
ignoreEmptyParagraphs: options.ignoreEmptyParagraphs,
|
|
166
|
-
idPrefix: options.idPrefix
|
|
167
|
-
};
|
|
168
|
-
if (options.transformDocument) mammothOptions.transformDocument = options.transformDocument;
|
|
290
|
+
const mammothOptions = Object.fromEntries(Object.entries(options.mammoth ?? {}).flatMap(([key, value]) => value !== void 0 ? [[key, value]] : []));
|
|
169
291
|
const html = await mammoth.convertToHtml(mammothInput, mammothOptions);
|
|
170
292
|
return this.convertHtml(html.value);
|
|
171
293
|
}
|
|
172
294
|
};
|
|
173
|
-
|
|
174
|
-
//#region src/converters/ipynb.ts
|
|
295
|
+
// src/converters/ipynb.ts
|
|
175
296
|
var IpynbConverter = class {
|
|
176
297
|
async convert(source, options = {}) {
|
|
177
|
-
if (options.
|
|
298
|
+
if (options.extension !== "ipynb") return null;
|
|
178
299
|
const notebook = JSON.parse(decodeText(source));
|
|
179
300
|
const output = [];
|
|
180
301
|
let title = notebook.metadata?.title ?? null;
|
|
@@ -189,99 +310,49 @@ var IpynbConverter = class {
|
|
|
189
310
|
const markdown = output.join("\n\n");
|
|
190
311
|
return {
|
|
191
312
|
title,
|
|
192
|
-
markdown
|
|
193
|
-
text_content: markdown
|
|
313
|
+
markdown
|
|
194
314
|
};
|
|
195
315
|
}
|
|
196
316
|
};
|
|
197
|
-
|
|
198
|
-
//#region src/converters/pdf.ts
|
|
317
|
+
// src/converters/pdf.ts
|
|
199
318
|
var PdfConverter = class {
|
|
200
319
|
async convert(source, options) {
|
|
201
|
-
if (options.
|
|
320
|
+
if (options.extension !== "pdf") return null;
|
|
202
321
|
const pdf = await getDocumentProxy(source.slice());
|
|
203
322
|
try {
|
|
204
323
|
const [{ text }, { info }] = await Promise.all([extractText(pdf, { mergePages: true }), getMeta(pdf)]);
|
|
205
324
|
return {
|
|
206
325
|
title: typeof info.Title === "string" && info.Title.trim() ? info.Title : null,
|
|
207
|
-
markdown: text
|
|
208
|
-
text_content: text
|
|
326
|
+
markdown: text
|
|
209
327
|
};
|
|
210
328
|
} finally {
|
|
211
329
|
await pdf.loadingTask.destroy();
|
|
212
330
|
}
|
|
213
331
|
}
|
|
214
332
|
};
|
|
215
|
-
|
|
216
|
-
//#region src/mime.ts
|
|
217
|
-
const MIME_EXTENSIONS = {
|
|
218
|
-
"application/atom+xml": ".atom",
|
|
219
|
-
"application/json": ".json",
|
|
220
|
-
"application/pdf": ".pdf",
|
|
221
|
-
"application/zip": ".zip",
|
|
222
|
-
"application/rss+xml": ".rss",
|
|
223
|
-
"application/x-pdf": ".pdf",
|
|
224
|
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
|
|
225
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
|
|
226
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
|
|
227
|
-
"application/xml": ".xml",
|
|
228
|
-
"text/csv": ".csv",
|
|
229
|
-
"text/html": ".html",
|
|
230
|
-
"text/markdown": ".md",
|
|
231
|
-
"text/plain": ".txt",
|
|
232
|
-
"text/xml": ".xml"
|
|
233
|
-
};
|
|
234
|
-
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
235
|
-
".csv",
|
|
236
|
-
".json",
|
|
237
|
-
".log",
|
|
238
|
-
".markdown",
|
|
239
|
-
".md",
|
|
240
|
-
".text",
|
|
241
|
-
".txt"
|
|
242
|
-
]);
|
|
243
|
-
function extensionFromMime(contentType) {
|
|
244
|
-
if (!contentType) return null;
|
|
245
|
-
const mime = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
246
|
-
return MIME_EXTENSIONS[mime] ?? (mime.startsWith("text/") ? ".txt" : null);
|
|
247
|
-
}
|
|
248
|
-
function isTextExtension(extension) {
|
|
249
|
-
return TEXT_EXTENSIONS.has(extension.toLowerCase());
|
|
250
|
-
}
|
|
251
|
-
function extensionFromPathname(pathname) {
|
|
252
|
-
const filename = pathname.split("/").pop() ?? "";
|
|
253
|
-
const dot = filename.lastIndexOf(".");
|
|
254
|
-
return dot > 0 ? filename.slice(dot).toLowerCase() : null;
|
|
255
|
-
}
|
|
256
|
-
//#endregion
|
|
257
|
-
//#region src/converters/plain-text.ts
|
|
333
|
+
// src/converters/plain-text.ts
|
|
258
334
|
var PlainTextConverter = class {
|
|
259
335
|
async convert(source, options = {}) {
|
|
260
|
-
if (!isTextExtension(options.
|
|
261
|
-
const content = decodeText(source);
|
|
336
|
+
if (!isTextExtension(options.extension ?? "")) return null;
|
|
262
337
|
return {
|
|
263
338
|
title: null,
|
|
264
|
-
markdown:
|
|
265
|
-
text_content: content
|
|
339
|
+
markdown: decodeText(source)
|
|
266
340
|
};
|
|
267
341
|
}
|
|
268
342
|
};
|
|
269
|
-
|
|
270
|
-
//#region src/converters/pptx.ts
|
|
343
|
+
// src/converters/pptx.ts
|
|
271
344
|
var PptxConverter = class extends HtmlConverter {
|
|
272
345
|
async convert(source, options) {
|
|
273
|
-
if (options.
|
|
346
|
+
if (options.extension !== "pptx") return null;
|
|
274
347
|
const slides = await pptxToHtml(toArrayBuffer(source), { domParserFactory: createPptxDomParser });
|
|
275
348
|
const sections = [];
|
|
276
349
|
for (const [index, html] of slides.entries()) {
|
|
277
350
|
const result = await this.convertHtml(`<html><body>${html}</body></html>`);
|
|
278
351
|
sections.push(`<!-- Slide number: ${index + 1} -->\n${result?.markdown.trim() ?? ""}`);
|
|
279
352
|
}
|
|
280
|
-
const markdown = sections.join("\n\n");
|
|
281
353
|
return {
|
|
282
354
|
title: null,
|
|
283
|
-
markdown
|
|
284
|
-
text_content: markdown
|
|
355
|
+
markdown: sections.join("\n\n")
|
|
285
356
|
};
|
|
286
357
|
}
|
|
287
358
|
};
|
|
@@ -345,32 +416,33 @@ function defineGetter(target, name, get) {
|
|
|
345
416
|
get
|
|
346
417
|
});
|
|
347
418
|
}
|
|
348
|
-
|
|
349
|
-
//#region src/converters/wikipedia.ts
|
|
419
|
+
// src/converters/wikipedia.ts
|
|
350
420
|
const WIKIPEDIA_URL = /^https?:\/\/[a-z]{2,3}\.wikipedia\.org\//i;
|
|
351
421
|
var WikipediaConverter = class {
|
|
352
422
|
async convert(source, options = {}) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
423
|
+
if (options.extension !== "html" && options.extension !== "htm" || !WIKIPEDIA_URL.test(options.url ?? "")) return null;
|
|
424
|
+
return parseHtmlDocument(decodeText(source), (document) => {
|
|
425
|
+
document.querySelectorAll("script, style").forEach((element) => {
|
|
426
|
+
element.remove();
|
|
427
|
+
});
|
|
428
|
+
const body = document.querySelector("div#mw-content-text");
|
|
429
|
+
return {
|
|
430
|
+
title: {
|
|
431
|
+
type: "metadata",
|
|
432
|
+
value: document.querySelector("span.mw-page-title-main")?.textContent?.trim() || document.title || null
|
|
433
|
+
},
|
|
434
|
+
markdown: {
|
|
435
|
+
type: "node",
|
|
436
|
+
value: body || document
|
|
437
|
+
}
|
|
438
|
+
};
|
|
358
439
|
});
|
|
359
|
-
const body = document.querySelector("div#mw-content-text");
|
|
360
|
-
const title = document.querySelector("span.mw-page-title-main")?.textContent?.trim() || document.title || null;
|
|
361
|
-
const markdown = body ? `# ${title ?? "Wikipedia"}\n\n${new CustomTurnDown().convertSoup(body)}` : new CustomTurnDown().convertSoup(document);
|
|
362
|
-
return {
|
|
363
|
-
title,
|
|
364
|
-
markdown,
|
|
365
|
-
text_content: markdown
|
|
366
|
-
};
|
|
367
440
|
}
|
|
368
441
|
};
|
|
369
|
-
|
|
370
|
-
//#region src/converters/xlsx.ts
|
|
442
|
+
// src/converters/xlsx.ts
|
|
371
443
|
var XlsxConverter = class extends HtmlConverter {
|
|
372
444
|
async convert(source, options) {
|
|
373
|
-
if (options.
|
|
445
|
+
if (options.extension !== "xlsx") return null;
|
|
374
446
|
const sheets = await readExcelFile(toArrayBuffer(source));
|
|
375
447
|
const sections = [];
|
|
376
448
|
for (const { sheet, data } of sheets) {
|
|
@@ -379,11 +451,9 @@ var XlsxConverter = class extends HtmlConverter {
|
|
|
379
451
|
const result = await this.convertHtml(html);
|
|
380
452
|
sections.push(`## ${sheet}\n${result?.markdown.trim() ?? ""}`);
|
|
381
453
|
}
|
|
382
|
-
const markdown = sections.join("\n\n");
|
|
383
454
|
return {
|
|
384
455
|
title: null,
|
|
385
|
-
markdown
|
|
386
|
-
text_content: markdown
|
|
456
|
+
markdown: sections.join("\n\n")
|
|
387
457
|
};
|
|
388
458
|
}
|
|
389
459
|
};
|
|
@@ -404,129 +474,126 @@ function cellText(value) {
|
|
|
404
474
|
function escapeHtml(value) {
|
|
405
475
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
406
476
|
}
|
|
407
|
-
|
|
408
|
-
//#region src/converters/xml-rss-atom.ts
|
|
477
|
+
// src/converters/xml-rss-atom.ts
|
|
409
478
|
var RSSConverter = class {
|
|
410
479
|
async convert(source, options = {}) {
|
|
411
|
-
|
|
412
|
-
if (![
|
|
413
|
-
".xml",
|
|
414
|
-
".rss",
|
|
415
|
-
".atom"
|
|
416
|
-
].includes(extension)) return null;
|
|
480
|
+
if (options.extension !== "xml" && options.extension !== "rss" && options.extension !== "atom") return null;
|
|
417
481
|
const document = new DOMParser().parseFromString(decodeText(source), "text/xml");
|
|
418
|
-
if (document.getElementsByTagName("rss").length > 0) return
|
|
419
|
-
if (document.getElementsByTagName("feed")[0]?.getElementsByTagName("entry").length) return
|
|
482
|
+
if (document.getElementsByTagName("rss").length > 0) return parseRss(document);
|
|
483
|
+
if (document.getElementsByTagName("feed")[0]?.getElementsByTagName("entry").length) return parseAtom(document);
|
|
420
484
|
return null;
|
|
421
485
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
markdown,
|
|
445
|
-
text_content: markdown
|
|
446
|
-
};
|
|
486
|
+
};
|
|
487
|
+
function parseAtom(document) {
|
|
488
|
+
const root = document.getElementsByTagName("feed")[0];
|
|
489
|
+
if (!root) return null;
|
|
490
|
+
const title = getText(root, "title");
|
|
491
|
+
const subtitle = getText(root, "subtitle");
|
|
492
|
+
const sections = [`# ${title ?? "Feed"}`];
|
|
493
|
+
if (subtitle) sections.push(subtitle);
|
|
494
|
+
for (const entry of [...root.getElementsByTagName("entry")]) {
|
|
495
|
+
const entryTitle = getText(entry, "title");
|
|
496
|
+
const updated = getText(entry, "updated");
|
|
497
|
+
const summary = getText(entry, "summary");
|
|
498
|
+
const content = getText(entry, "content");
|
|
499
|
+
const lines = [];
|
|
500
|
+
if (entryTitle) lines.push(`## ${entryTitle}`);
|
|
501
|
+
if (updated) lines.push(`Updated on: ${updated}`);
|
|
502
|
+
const node = summary || content;
|
|
503
|
+
if (node) lines.push(parseHtmlDocument(node, (document) => ({ markdown: {
|
|
504
|
+
type: "node",
|
|
505
|
+
value: document.documentElement
|
|
506
|
+
} })).markdown);
|
|
507
|
+
sections.push(lines.join("\n\n"));
|
|
447
508
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
};
|
|
509
|
+
return {
|
|
510
|
+
title,
|
|
511
|
+
markdown: sections.join("\n\n")
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function parseRss(document) {
|
|
515
|
+
const channel = document.getElementsByTagName("rss")[0]?.getElementsByTagName("channel")[0];
|
|
516
|
+
if (!channel) return null;
|
|
517
|
+
const title = getText(channel, "title");
|
|
518
|
+
const description = getText(channel, "description");
|
|
519
|
+
const sections = title ? [`# ${title}`] : [];
|
|
520
|
+
if (description) sections.push(description);
|
|
521
|
+
for (const item of [...channel.getElementsByTagName("item")]) {
|
|
522
|
+
const itemTitle = getText(item, "title");
|
|
523
|
+
const published = getText(item, "pubDate");
|
|
524
|
+
const itemDescription = getText(item, "description");
|
|
525
|
+
const content = getText(item, "content:encoded");
|
|
526
|
+
const lines = [];
|
|
527
|
+
if (itemTitle) lines.push(`## ${itemTitle}`);
|
|
528
|
+
if (published) lines.push(`Published on: ${published}`);
|
|
529
|
+
const node = itemDescription || content;
|
|
530
|
+
if (node) lines.push(parseHtmlDocument(node, (document) => ({ markdown: {
|
|
531
|
+
type: "node",
|
|
532
|
+
value: document.documentElement
|
|
533
|
+
} })).markdown);
|
|
534
|
+
sections.push(lines.join("\n\n"));
|
|
473
535
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
536
|
+
return {
|
|
537
|
+
title,
|
|
538
|
+
markdown: sections.join("\n\n")
|
|
539
|
+
};
|
|
477
540
|
}
|
|
478
541
|
function getText(element, tagName) {
|
|
479
542
|
return element.getElementsByTagName(tagName)[0]?.textContent?.trim() || null;
|
|
480
543
|
}
|
|
481
|
-
|
|
482
|
-
|
|
544
|
+
// src/converters/youtube.ts
|
|
545
|
+
const YOUTUBE_URL = /^https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=|shorts\/)?([a-zA-Z0-9_-]+)/i;
|
|
483
546
|
var YouTubeConverter = class {
|
|
484
547
|
async convert(source, options = {}) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
548
|
+
if (options.extension !== "html" && options.extension !== "htm" || !YOUTUBE_URL.test(options.url ?? "")) return null;
|
|
549
|
+
return parseHtmlDocument(decodeText(source), (document) => {
|
|
550
|
+
const metadata = {};
|
|
551
|
+
document.querySelectorAll("meta").forEach((meta) => {
|
|
552
|
+
const content = meta.getAttribute("content");
|
|
553
|
+
const key = meta.getAttribute("itemprop") ?? meta.getAttribute("property") ?? meta.getAttribute("name");
|
|
554
|
+
if (key && content) metadata[key] = content;
|
|
555
|
+
});
|
|
556
|
+
try {
|
|
557
|
+
for (const script of document.querySelectorAll("script")) {
|
|
558
|
+
const content = script.textContent ?? "";
|
|
559
|
+
if (!content.includes("ytInitialData")) continue;
|
|
560
|
+
const start = content.indexOf("{");
|
|
561
|
+
const end = content.lastIndexOf("}");
|
|
562
|
+
if (start >= 0 && end > start) {
|
|
563
|
+
const description = findKey(JSON.parse(content.slice(start, end + 1)), "attributedDescriptionBodyText");
|
|
564
|
+
if (isRecord(description) && typeof description.content === "string") metadata.description = description.content;
|
|
565
|
+
}
|
|
566
|
+
break;
|
|
503
567
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
"
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
568
|
+
} catch {}
|
|
569
|
+
const title = first(metadata, [
|
|
570
|
+
"title",
|
|
571
|
+
"og:title",
|
|
572
|
+
"name"
|
|
573
|
+
]) || document.title || null;
|
|
574
|
+
const lines = ["# YouTube"];
|
|
575
|
+
if (title) lines.push(`## ${title}`);
|
|
576
|
+
const stats = [];
|
|
577
|
+
const views = first(metadata, ["interactionCount"]);
|
|
578
|
+
const keywords = first(metadata, ["keywords"]);
|
|
579
|
+
const runtime = first(metadata, ["duration"]);
|
|
580
|
+
if (views) stats.push(`- **Views:** ${views}`);
|
|
581
|
+
if (keywords) stats.push(`- **Keywords:** ${keywords}`);
|
|
582
|
+
if (runtime) stats.push(`- **Runtime:** ${runtime}`);
|
|
583
|
+
if (stats.length > 0) lines.push(`### Video Metadata\n\n${stats.join("\n")}`);
|
|
584
|
+
const description = first(metadata, ["description", "og:description"]);
|
|
585
|
+
if (description) lines.push(`### Description\n\n${description}`);
|
|
586
|
+
return {
|
|
587
|
+
title: {
|
|
588
|
+
type: "metadata",
|
|
589
|
+
value: title
|
|
590
|
+
},
|
|
591
|
+
markdown: {
|
|
592
|
+
type: "metadata",
|
|
593
|
+
value: lines.join("\n\n")
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
});
|
|
530
597
|
}
|
|
531
598
|
};
|
|
532
599
|
function first(metadata, keys) {
|
|
@@ -549,23 +616,20 @@ function findKey(value, key) {
|
|
|
549
616
|
function isRecord(value) {
|
|
550
617
|
return typeof value === "object" && value !== null;
|
|
551
618
|
}
|
|
552
|
-
|
|
553
|
-
//#region src/converters/zip.ts
|
|
619
|
+
// src/converters/zip.ts
|
|
554
620
|
var ZipConverter = class {
|
|
555
621
|
async convert(source, options = {}) {
|
|
556
|
-
if (options.
|
|
622
|
+
if (options.extension !== "zip") return null;
|
|
557
623
|
const files = unzipSync(source, { filter: (file) => !file.name.endsWith("/") });
|
|
558
|
-
const converters = options.
|
|
624
|
+
const converters = options._converters ?? [];
|
|
559
625
|
const sections = [`Content from the zip file \`${options.url ?? "archive.zip"}\`:`];
|
|
560
626
|
for (const [name, bytes] of Object.entries(files)) {
|
|
561
627
|
const result = await convertEntry(bytes, name, converters, options);
|
|
562
628
|
if (result) sections.push(`## File: ${name}\n\n${result.markdown}`);
|
|
563
629
|
}
|
|
564
|
-
const markdown = sections.join("\n\n");
|
|
565
630
|
return {
|
|
566
631
|
title: null,
|
|
567
|
-
markdown
|
|
568
|
-
text_content: markdown
|
|
632
|
+
markdown: sections.join("\n\n")
|
|
569
633
|
};
|
|
570
634
|
}
|
|
571
635
|
};
|
|
@@ -574,9 +638,9 @@ async function convertEntry(source, name, converters, options) {
|
|
|
574
638
|
if (!extension) return null;
|
|
575
639
|
const entryOptions = {
|
|
576
640
|
...options,
|
|
577
|
-
|
|
641
|
+
extension,
|
|
578
642
|
url: void 0,
|
|
579
|
-
|
|
643
|
+
_converters: converters
|
|
580
644
|
};
|
|
581
645
|
for (const converter of converters) try {
|
|
582
646
|
const result = await converter.convert(source, entryOptions);
|
|
@@ -584,42 +648,38 @@ async function convertEntry(source, name, converters, options) {
|
|
|
584
648
|
} catch {}
|
|
585
649
|
return null;
|
|
586
650
|
}
|
|
587
|
-
|
|
588
|
-
//#region src/markitdown.ts
|
|
651
|
+
// src/markitdown.ts
|
|
589
652
|
var MarkItDown = class {
|
|
653
|
+
converters = [];
|
|
590
654
|
constructor() {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
655
|
+
const ordered = [
|
|
656
|
+
PlainTextConverter,
|
|
657
|
+
HtmlConverter,
|
|
658
|
+
ZipConverter,
|
|
659
|
+
RSSConverter,
|
|
660
|
+
WikipediaConverter,
|
|
661
|
+
YouTubeConverter,
|
|
662
|
+
BingSerpConverter,
|
|
663
|
+
DocxConverter,
|
|
664
|
+
XlsxConverter,
|
|
665
|
+
PptxConverter,
|
|
666
|
+
PdfConverter,
|
|
667
|
+
IpynbConverter
|
|
668
|
+
];
|
|
669
|
+
for (const converter of ordered) this.registerConverter(new converter());
|
|
604
670
|
}
|
|
605
671
|
/** Convert a URL, response, blob, array buffer, data view, or typed array. */
|
|
606
672
|
async convert(source, options = {}) {
|
|
607
673
|
if (typeof source === "string" || source instanceof URL) return this.convertUrl(source.toString(), options);
|
|
608
674
|
if (isResponse(source)) return this.convertResponse(source, options);
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
return this.convertBytes(
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* @deprecated Pass the byte source directly to `convert`.
|
|
620
|
-
*/
|
|
621
|
-
async convertBuffer(source, options) {
|
|
622
|
-
return this.convert(source, options);
|
|
675
|
+
const bytes = await toUint8Array(source);
|
|
676
|
+
const extensions = await detectExtensions(bytes, {
|
|
677
|
+
extension: options.extension,
|
|
678
|
+
filename: isBlob(source) && "name" in source ? String(source.name) : null,
|
|
679
|
+
urls: [options.url],
|
|
680
|
+
contentType: isBlob(source) ? source.type : null
|
|
681
|
+
});
|
|
682
|
+
return this.convertBytes(bytes, extensions, options);
|
|
623
683
|
}
|
|
624
684
|
async convertUrl(source, { fetch: fetchImplementation = globalThis.fetch, ...options }) {
|
|
625
685
|
const url = new URL(source);
|
|
@@ -633,44 +693,39 @@ var MarkItDown = class {
|
|
|
633
693
|
});
|
|
634
694
|
}
|
|
635
695
|
async convertResponse(response, options) {
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
return this.convertBytes(new Uint8Array(await response.arrayBuffer()), extensions, {
|
|
696
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
697
|
+
const extensions = await detectExtensions(bytes, {
|
|
698
|
+
extension: options.extension,
|
|
699
|
+
filename: filenameFromDisposition(response.headers.get("content-disposition")),
|
|
700
|
+
urls: [options.url, response.url],
|
|
701
|
+
contentType: response.headers.get("content-type")
|
|
702
|
+
});
|
|
703
|
+
return this.convertBytes(bytes, extensions, {
|
|
645
704
|
...options,
|
|
646
|
-
url:
|
|
705
|
+
url: options.url || response.url || void 0
|
|
647
706
|
});
|
|
648
707
|
}
|
|
649
708
|
async convertBytes(source, extensions, options) {
|
|
709
|
+
if (extensions.length === 0) throw new Error("Could not determine the file type. Provide `extension` for better results.");
|
|
650
710
|
let lastError;
|
|
651
711
|
for (const extension of extensions) for (const converter of this.converters) try {
|
|
652
712
|
const result = await converter.convert(source, {
|
|
653
713
|
...options,
|
|
654
|
-
|
|
655
|
-
|
|
714
|
+
extension,
|
|
715
|
+
_converters: this.converters
|
|
656
716
|
});
|
|
657
717
|
if (result) {
|
|
658
718
|
result.markdown = result.markdown.replace(/\r\n|\r|\n/g, "\n").trim().replace(/\n{3,}/g, "\n\n");
|
|
659
|
-
result.text_content = result.markdown;
|
|
660
719
|
return result;
|
|
661
720
|
}
|
|
662
721
|
} catch (error) {
|
|
663
722
|
if (options.debug) console.error("Converter threw error:", error);
|
|
664
723
|
lastError = error;
|
|
665
724
|
}
|
|
666
|
-
const formats =
|
|
725
|
+
const formats = extensions.join(", ");
|
|
667
726
|
if (lastError) throw new Error(`Could not convert ${formats} to Markdown: ${String(lastError)}`);
|
|
668
727
|
throw new Error(`Unsupported file type: ${formats}.`);
|
|
669
728
|
}
|
|
670
|
-
addExtension(extensions, extension) {
|
|
671
|
-
if (!extension) return;
|
|
672
|
-
extensions.add(extension.startsWith(".") ? extension.toLowerCase() : `.${extension.toLowerCase()}`);
|
|
673
|
-
}
|
|
674
729
|
registerConverter(converter) {
|
|
675
730
|
this.converters.unshift(converter);
|
|
676
731
|
}
|
|
@@ -678,13 +733,53 @@ var MarkItDown = class {
|
|
|
678
733
|
function isResponse(value) {
|
|
679
734
|
return typeof Response !== "undefined" && value instanceof Response;
|
|
680
735
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
})
|
|
689
|
-
|
|
690
|
-
|
|
736
|
+
// src/cli.ts
|
|
737
|
+
const parser = object({
|
|
738
|
+
file: argument(path({
|
|
739
|
+
mustExist: true,
|
|
740
|
+
type: "file",
|
|
741
|
+
metavar: "FILE"
|
|
742
|
+
}), { description: message`File to convert.` }),
|
|
743
|
+
extension: optional(option("-x", "--extension", string({ metavar: "EXT" }), { description: message`File type to convert from, when it can't be detected from the file.` })),
|
|
744
|
+
json: withDefault(flag("--json", { description: message`Print the title and Markdown as JSON instead of only the Markdown.` }), false)
|
|
745
|
+
});
|
|
746
|
+
const program = defineProgram({
|
|
747
|
+
parser,
|
|
748
|
+
metadata: {
|
|
749
|
+
name: "markitdown",
|
|
750
|
+
version,
|
|
751
|
+
brief: message`Convert files to Markdown`,
|
|
752
|
+
description: message`Converts PDF, Word, Excel, PowerPoint, HTML, notebooks, feeds, zip archives and text files to Markdown. The file type is detected from the file's name and contents.`,
|
|
753
|
+
examples: message`Convert a document:
|
|
754
|
+
|
|
755
|
+
${commandLine("markitdown report.docx")}
|
|
756
|
+
|
|
757
|
+
Convert a file without an extension as HTML:
|
|
758
|
+
|
|
759
|
+
${commandLine("markitdown --extension html page")}`,
|
|
760
|
+
author: message`${author}`,
|
|
761
|
+
bugs: message`Report bugs at ${url(bugs.url)}.`
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
if (import.meta.main) {
|
|
765
|
+
const argv = process.argv.slice(2);
|
|
766
|
+
const parsed = runSync(program, {
|
|
767
|
+
args: argv.length ? argv : ["--help"],
|
|
768
|
+
aboveError: "none",
|
|
769
|
+
help: "both",
|
|
770
|
+
version: {
|
|
771
|
+
value: version,
|
|
772
|
+
option: true
|
|
773
|
+
},
|
|
774
|
+
completion: "both"
|
|
775
|
+
});
|
|
776
|
+
try {
|
|
777
|
+
const bytes = await readFile(parsed.file);
|
|
778
|
+
const result = await new MarkItDown().convert(new File([bytes], basename(parsed.file)), parsed.extension ? { extension: parsed.extension } : {});
|
|
779
|
+
console.log(parsed.json ? JSON.stringify(result, null, 2) : result?.markdown);
|
|
780
|
+
} catch (error) {
|
|
781
|
+
console.error(`Error: ${error.message}`);
|
|
782
|
+
process.exitCode = 1;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
export { program };
|