@ryangarber/markitdown-ts 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -10
- package/dist/cli.mjs +516 -342
- package/dist/index.cjs +12057 -429
- 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 +490 -329
- package/dist/index.js.map +1 -1
- package/dist/markitdown.1 +30 -0
- package/package.json +40 -20
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.ts.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,17 +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
|
-
import
|
|
9
|
-
import { DOMParser } from "@xmldom/xmldom";
|
|
17
|
+
import { pptxToHtml } from "@jvmr/pptx-to-html";
|
|
18
|
+
import { DOMParser, Document, Element, Node } from "@xmldom/xmldom";
|
|
19
|
+
import readExcelFile from "read-excel-file/universal";
|
|
10
20
|
import { unzipSync } from "fflate";
|
|
11
|
-
|
|
12
|
-
var version = "0.1.
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
// package.json
|
|
22
|
+
var version = "0.1.5";
|
|
23
|
+
var bugs = { "url": "https://github.com/ryangarber/markitdown-ts/issues" };
|
|
24
|
+
var author = "Ryan Garber";
|
|
25
|
+
// src/utils/bytes.ts
|
|
15
26
|
async function toUint8Array(source) {
|
|
16
27
|
if (source instanceof Uint8Array) return source;
|
|
17
28
|
if (isBlob(source)) return new Uint8Array(await source.arrayBuffer());
|
|
@@ -27,12 +38,11 @@ function decodeText(source) {
|
|
|
27
38
|
function isBlob(value) {
|
|
28
39
|
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
29
40
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
var CustomTurnDown = class {
|
|
41
|
+
// src/utils/turndown.ts
|
|
42
|
+
var Turndown = class {
|
|
33
43
|
convertSoup(document) {
|
|
34
44
|
const service = new TurndownService({ headingStyle: "atx" });
|
|
35
|
-
service.use(
|
|
45
|
+
service.use(TurndownPluginGfm);
|
|
36
46
|
service.addRule("anchor tags", {
|
|
37
47
|
filter: ["a"],
|
|
38
48
|
replacement(content, node) {
|
|
@@ -67,48 +77,166 @@ var CustomTurnDown = class {
|
|
|
67
77
|
return ``;
|
|
68
78
|
}
|
|
69
79
|
});
|
|
70
|
-
return service.turndown(document);
|
|
80
|
+
return service.turndown(serializeNode(document));
|
|
71
81
|
}
|
|
72
82
|
};
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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;
|
|
77
155
|
}
|
|
78
|
-
|
|
79
|
-
|
|
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/");
|
|
203
|
+
}
|
|
204
|
+
// src/converters/bingserp.ts
|
|
80
205
|
const BING_SEARCH_URL = /^https:\/\/www\.bing\.com\/search\?q=/i;
|
|
81
206
|
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
82
207
|
var BingSerpConverter = class {
|
|
83
208
|
async convert(source, options = {}) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (element.textContent) element.textContent += " ";
|
|
91
|
-
});
|
|
92
|
-
document.querySelectorAll(".algoSlug_icon").forEach((element) => {
|
|
93
|
-
element.remove();
|
|
94
|
-
});
|
|
95
|
-
const turndown = new CustomTurnDown();
|
|
96
|
-
const markdown = `## A Bing search for '${query}' found the following results:\n\n${[...document.querySelectorAll(".b_algo")].map((result) => {
|
|
97
|
-
result.querySelectorAll("a[href]").forEach((anchor) => {
|
|
98
|
-
const href = anchor.getAttribute("href");
|
|
99
|
-
if (!href) return;
|
|
100
|
-
try {
|
|
101
|
-
const encoded = new URL(href, url).searchParams.get("u");
|
|
102
|
-
if (encoded) anchor.setAttribute("href", decodeBingUrl(encoded));
|
|
103
|
-
} 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 += " ";
|
|
104
215
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
});
|
|
112
240
|
}
|
|
113
241
|
};
|
|
114
242
|
function decodeBingUrl(encoded) {
|
|
@@ -128,52 +256,46 @@ function decodeBingUrl(encoded) {
|
|
|
128
256
|
}
|
|
129
257
|
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
130
258
|
}
|
|
131
|
-
|
|
132
|
-
//#region src/converters/html.ts
|
|
259
|
+
// src/converters/html.ts
|
|
133
260
|
var HtmlConverter = class {
|
|
134
261
|
async convert(source, options) {
|
|
135
|
-
|
|
136
|
-
if (extension !== ".html" && extension !== ".htm") return null;
|
|
262
|
+
if (options.extension !== "html" && options.extension !== "htm") return null;
|
|
137
263
|
return this.convertHtml(decodeText(source));
|
|
138
264
|
}
|
|
139
265
|
async convertHtml(html) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
};
|
|
144
281
|
});
|
|
145
|
-
const markdown = new CustomTurnDown().convertSoup(document);
|
|
146
|
-
return {
|
|
147
|
-
title: title || null,
|
|
148
|
-
markdown,
|
|
149
|
-
text_content: markdown
|
|
150
|
-
};
|
|
151
282
|
}
|
|
152
283
|
};
|
|
153
|
-
|
|
154
|
-
//#region src/converters/docx.ts
|
|
284
|
+
// src/converters/docx.ts
|
|
155
285
|
var DocxConverter = class extends HtmlConverter {
|
|
156
286
|
async convert(source, options) {
|
|
157
|
-
if (options.
|
|
287
|
+
if (options.extension !== "docx") return null;
|
|
158
288
|
let mammothInput = { arrayBuffer: toArrayBuffer(source) };
|
|
159
289
|
if (typeof process !== "undefined" && process.versions !== null && process.versions.node !== null) mammothInput = { buffer: Buffer.from(source) };
|
|
160
|
-
const mammothOptions = {
|
|
161
|
-
styleMap: options.styleMap,
|
|
162
|
-
includeEmbeddedStyleMap: options.includeEmbeddedStyleMap,
|
|
163
|
-
includeDefaultStyleMap: options.includeDefaultStyleMap,
|
|
164
|
-
ignoreEmptyParagraphs: options.ignoreEmptyParagraphs,
|
|
165
|
-
idPrefix: options.idPrefix
|
|
166
|
-
};
|
|
167
|
-
if (options.transformDocument) mammothOptions.transformDocument = options.transformDocument;
|
|
290
|
+
const mammothOptions = Object.fromEntries(Object.entries(options.mammoth ?? {}).flatMap(([key, value]) => value !== void 0 ? [[key, value]] : []));
|
|
168
291
|
const html = await mammoth.convertToHtml(mammothInput, mammothOptions);
|
|
169
292
|
return this.convertHtml(html.value);
|
|
170
293
|
}
|
|
171
294
|
};
|
|
172
|
-
|
|
173
|
-
//#region src/converters/ipynb.ts
|
|
295
|
+
// src/converters/ipynb.ts
|
|
174
296
|
var IpynbConverter = class {
|
|
175
297
|
async convert(source, options = {}) {
|
|
176
|
-
if (options.
|
|
298
|
+
if (options.extension !== "ipynb") return null;
|
|
177
299
|
const notebook = JSON.parse(decodeText(source));
|
|
178
300
|
const output = [];
|
|
179
301
|
let title = notebook.metadata?.title ?? null;
|
|
@@ -188,267 +310,290 @@ var IpynbConverter = class {
|
|
|
188
310
|
const markdown = output.join("\n\n");
|
|
189
311
|
return {
|
|
190
312
|
title,
|
|
191
|
-
markdown
|
|
192
|
-
text_content: markdown
|
|
313
|
+
markdown
|
|
193
314
|
};
|
|
194
315
|
}
|
|
195
316
|
};
|
|
196
|
-
|
|
197
|
-
//#region src/converters/pdf.ts
|
|
317
|
+
// src/converters/pdf.ts
|
|
198
318
|
var PdfConverter = class {
|
|
199
319
|
async convert(source, options) {
|
|
200
|
-
if (options.
|
|
320
|
+
if (options.extension !== "pdf") return null;
|
|
201
321
|
const pdf = await getDocumentProxy(source.slice());
|
|
202
322
|
try {
|
|
203
323
|
const [{ text }, { info }] = await Promise.all([extractText(pdf, { mergePages: true }), getMeta(pdf)]);
|
|
204
324
|
return {
|
|
205
325
|
title: typeof info.Title === "string" && info.Title.trim() ? info.Title : null,
|
|
206
|
-
markdown: text
|
|
207
|
-
text_content: text
|
|
326
|
+
markdown: text
|
|
208
327
|
};
|
|
209
328
|
} finally {
|
|
210
329
|
await pdf.loadingTask.destroy();
|
|
211
330
|
}
|
|
212
331
|
}
|
|
213
332
|
};
|
|
214
|
-
|
|
215
|
-
//#region src/mime.ts
|
|
216
|
-
const MIME_EXTENSIONS = {
|
|
217
|
-
"application/atom+xml": ".atom",
|
|
218
|
-
"application/json": ".json",
|
|
219
|
-
"application/pdf": ".pdf",
|
|
220
|
-
"application/zip": ".zip",
|
|
221
|
-
"application/rss+xml": ".rss",
|
|
222
|
-
"application/x-pdf": ".pdf",
|
|
223
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
|
|
224
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
|
|
225
|
-
"application/xml": ".xml",
|
|
226
|
-
"text/csv": ".csv",
|
|
227
|
-
"text/html": ".html",
|
|
228
|
-
"text/markdown": ".md",
|
|
229
|
-
"text/plain": ".txt",
|
|
230
|
-
"text/xml": ".xml"
|
|
231
|
-
};
|
|
232
|
-
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
233
|
-
".csv",
|
|
234
|
-
".json",
|
|
235
|
-
".log",
|
|
236
|
-
".markdown",
|
|
237
|
-
".md",
|
|
238
|
-
".text",
|
|
239
|
-
".txt"
|
|
240
|
-
]);
|
|
241
|
-
function extensionFromMime(contentType) {
|
|
242
|
-
if (!contentType) return null;
|
|
243
|
-
const mime = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
244
|
-
return MIME_EXTENSIONS[mime] ?? (mime.startsWith("text/") ? ".txt" : null);
|
|
245
|
-
}
|
|
246
|
-
function isTextExtension(extension) {
|
|
247
|
-
return TEXT_EXTENSIONS.has(extension.toLowerCase());
|
|
248
|
-
}
|
|
249
|
-
function extensionFromPathname(pathname) {
|
|
250
|
-
const filename = pathname.split("/").pop() ?? "";
|
|
251
|
-
const dot = filename.lastIndexOf(".");
|
|
252
|
-
return dot > 0 ? filename.slice(dot).toLowerCase() : null;
|
|
253
|
-
}
|
|
254
|
-
//#endregion
|
|
255
|
-
//#region src/converters/plain-text.ts
|
|
333
|
+
// src/converters/plain-text.ts
|
|
256
334
|
var PlainTextConverter = class {
|
|
257
335
|
async convert(source, options = {}) {
|
|
258
|
-
if (!isTextExtension(options.
|
|
259
|
-
|
|
336
|
+
if (!isTextExtension(options.extension ?? "")) return null;
|
|
337
|
+
return {
|
|
338
|
+
title: null,
|
|
339
|
+
markdown: decodeText(source)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
// src/converters/pptx.ts
|
|
344
|
+
var PptxConverter = class extends HtmlConverter {
|
|
345
|
+
async convert(source, options) {
|
|
346
|
+
if (options.extension !== "pptx") return null;
|
|
347
|
+
const slides = await pptxToHtml(toArrayBuffer(source), { domParserFactory: createPptxDomParser });
|
|
348
|
+
const sections = [];
|
|
349
|
+
for (const [index, html] of slides.entries()) {
|
|
350
|
+
const result = await this.convertHtml(`<html><body>${html}</body></html>`);
|
|
351
|
+
sections.push(`<!-- Slide number: ${index + 1} -->\n${result?.markdown.trim() ?? ""}`);
|
|
352
|
+
}
|
|
260
353
|
return {
|
|
261
354
|
title: null,
|
|
262
|
-
markdown:
|
|
263
|
-
text_content: content
|
|
355
|
+
markdown: sections.join("\n\n")
|
|
264
356
|
};
|
|
265
357
|
}
|
|
266
358
|
};
|
|
267
|
-
|
|
268
|
-
|
|
359
|
+
function createPptxDomParser() {
|
|
360
|
+
if (typeof globalThis.DOMParser === "function") return new globalThis.DOMParser();
|
|
361
|
+
patchXmlDom();
|
|
362
|
+
return new DOMParser();
|
|
363
|
+
}
|
|
364
|
+
let xmlDomPatched = false;
|
|
365
|
+
function patchXmlDom() {
|
|
366
|
+
if (xmlDomPatched) return;
|
|
367
|
+
xmlDomPatched = true;
|
|
368
|
+
defineMethod(Element.prototype, "querySelector", querySelector);
|
|
369
|
+
defineMethod(Element.prototype, "querySelectorAll", querySelectorAll);
|
|
370
|
+
defineMethod(Document.prototype, "querySelector", querySelector);
|
|
371
|
+
defineMethod(Document.prototype, "querySelectorAll", querySelectorAll);
|
|
372
|
+
defineGetter(Element.prototype, "children", function() {
|
|
373
|
+
return [...this.childNodes].filter((node) => node.nodeType === 1);
|
|
374
|
+
});
|
|
375
|
+
defineGetter(Node.prototype, "parentElement", function() {
|
|
376
|
+
const parent = this.parentNode;
|
|
377
|
+
return parent?.nodeType === 1 ? parent : null;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function querySelector(selector) {
|
|
381
|
+
for (const group of selector.split(",")) {
|
|
382
|
+
const match = queryByPath(this, group.trim());
|
|
383
|
+
if (match) return match;
|
|
384
|
+
}
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
function querySelectorAll() {
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
function queryByPath(root, selector) {
|
|
391
|
+
const tokens = selector.split(/\s+/).filter(Boolean);
|
|
392
|
+
let current = [root];
|
|
393
|
+
for (const token of tokens) {
|
|
394
|
+
const localName = selectorLocalName(token);
|
|
395
|
+
const next = [];
|
|
396
|
+
for (const node of current) next.push(...Array.from(node.getElementsByTagNameNS("*", localName)));
|
|
397
|
+
current = next;
|
|
398
|
+
}
|
|
399
|
+
return current[0] ?? null;
|
|
400
|
+
}
|
|
401
|
+
function selectorLocalName(token) {
|
|
402
|
+
if (token.includes("|")) return token.slice(token.lastIndexOf("|") + 1);
|
|
403
|
+
return token.replace(/\\:/g, ":").split(":").pop() ?? token;
|
|
404
|
+
}
|
|
405
|
+
function defineMethod(target, name, value) {
|
|
406
|
+
Object.defineProperty(target, name, {
|
|
407
|
+
configurable: true,
|
|
408
|
+
writable: true,
|
|
409
|
+
value
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
function defineGetter(target, name, get) {
|
|
413
|
+
if (Object.getOwnPropertyDescriptor(target, name)) return;
|
|
414
|
+
Object.defineProperty(target, name, {
|
|
415
|
+
configurable: true,
|
|
416
|
+
get
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
// src/converters/wikipedia.ts
|
|
269
420
|
const WIKIPEDIA_URL = /^https?:\/\/[a-z]{2,3}\.wikipedia\.org\//i;
|
|
270
421
|
var WikipediaConverter = class {
|
|
271
422
|
async convert(source, options = {}) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
+
};
|
|
277
439
|
});
|
|
278
|
-
const body = document.querySelector("div#mw-content-text");
|
|
279
|
-
const title = document.querySelector("span.mw-page-title-main")?.textContent?.trim() || document.title || null;
|
|
280
|
-
const markdown = body ? `# ${title ?? "Wikipedia"}\n\n${new CustomTurnDown().convertSoup(body)}` : new CustomTurnDown().convertSoup(document);
|
|
281
|
-
return {
|
|
282
|
-
title,
|
|
283
|
-
markdown,
|
|
284
|
-
text_content: markdown
|
|
285
|
-
};
|
|
286
440
|
}
|
|
287
441
|
};
|
|
288
|
-
|
|
289
|
-
//#region src/converters/xlsx.ts
|
|
290
|
-
const { Workbook } = ExcelJS;
|
|
442
|
+
// src/converters/xlsx.ts
|
|
291
443
|
var XlsxConverter = class extends HtmlConverter {
|
|
292
444
|
async convert(source, options) {
|
|
293
|
-
if (options.
|
|
294
|
-
const
|
|
295
|
-
await workbook.xlsx.load(toArrayBuffer(source));
|
|
445
|
+
if (options.extension !== "xlsx") return null;
|
|
446
|
+
const sheets = await readExcelFile(toArrayBuffer(source));
|
|
296
447
|
const sections = [];
|
|
297
|
-
for (const
|
|
298
|
-
const html =
|
|
448
|
+
for (const { sheet, data } of sheets) {
|
|
449
|
+
const html = sheetToHtml(data);
|
|
299
450
|
if (!html) continue;
|
|
300
451
|
const result = await this.convertHtml(html);
|
|
301
|
-
sections.push(`## ${
|
|
452
|
+
sections.push(`## ${sheet}\n${result?.markdown.trim() ?? ""}`);
|
|
302
453
|
}
|
|
303
|
-
const markdown = sections.join("\n\n");
|
|
304
454
|
return {
|
|
305
|
-
title:
|
|
306
|
-
markdown
|
|
307
|
-
text_content: markdown
|
|
455
|
+
title: null,
|
|
456
|
+
markdown: sections.join("\n\n")
|
|
308
457
|
};
|
|
309
458
|
}
|
|
310
459
|
};
|
|
311
|
-
function
|
|
312
|
-
const columnCount =
|
|
313
|
-
if (!columnCount ||
|
|
314
|
-
|
|
315
|
-
worksheet.eachRow({ includeEmpty: true }, (row) => {
|
|
460
|
+
function sheetToHtml(rows) {
|
|
461
|
+
const columnCount = rows.reduce((max, row) => Math.max(max, row.length), 0);
|
|
462
|
+
if (!columnCount || rows.length === 0) return "";
|
|
463
|
+
return `<html><body><table>${rows.map((row) => {
|
|
316
464
|
const cells = [];
|
|
317
|
-
for (let column =
|
|
318
|
-
|
|
319
|
-
})
|
|
320
|
-
return `<html><body><table>${rows.join("")}</table></body></html>`;
|
|
465
|
+
for (let column = 0; column < columnCount; column++) cells.push(`<td>${escapeHtml(cellText(row[column]))}</td>`);
|
|
466
|
+
return `<tr>${cells.join("")}</tr>`;
|
|
467
|
+
}).join("")}</table></body></html>`;
|
|
321
468
|
}
|
|
322
|
-
function cellText(
|
|
323
|
-
if (
|
|
324
|
-
return
|
|
469
|
+
function cellText(value) {
|
|
470
|
+
if (value == null) return "";
|
|
471
|
+
if (value instanceof Date) return value.toISOString();
|
|
472
|
+
return String(value);
|
|
325
473
|
}
|
|
326
474
|
function escapeHtml(value) {
|
|
327
475
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
328
476
|
}
|
|
329
|
-
|
|
330
|
-
//#region src/converters/xml-rss-atom.ts
|
|
477
|
+
// src/converters/xml-rss-atom.ts
|
|
331
478
|
var RSSConverter = class {
|
|
332
479
|
async convert(source, options = {}) {
|
|
333
|
-
|
|
334
|
-
if (![
|
|
335
|
-
".xml",
|
|
336
|
-
".rss",
|
|
337
|
-
".atom"
|
|
338
|
-
].includes(extension)) return null;
|
|
480
|
+
if (options.extension !== "xml" && options.extension !== "rss" && options.extension !== "atom") return null;
|
|
339
481
|
const document = new DOMParser().parseFromString(decodeText(source), "text/xml");
|
|
340
|
-
if (document.getElementsByTagName("rss").length > 0) return
|
|
341
|
-
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);
|
|
342
484
|
return null;
|
|
343
485
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
markdown,
|
|
367
|
-
text_content: markdown
|
|
368
|
-
};
|
|
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"));
|
|
369
508
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
};
|
|
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"));
|
|
395
535
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
536
|
+
return {
|
|
537
|
+
title,
|
|
538
|
+
markdown: sections.join("\n\n")
|
|
539
|
+
};
|
|
399
540
|
}
|
|
400
541
|
function getText(element, tagName) {
|
|
401
542
|
return element.getElementsByTagName(tagName)[0]?.textContent?.trim() || null;
|
|
402
543
|
}
|
|
403
|
-
|
|
404
|
-
|
|
544
|
+
// src/converters/youtube.ts
|
|
545
|
+
const YOUTUBE_URL = /^https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=|shorts\/)?([a-zA-Z0-9_-]+)/i;
|
|
405
546
|
var YouTubeConverter = class {
|
|
406
547
|
async convert(source, options = {}) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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;
|
|
425
567
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
"
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
+
});
|
|
452
597
|
}
|
|
453
598
|
};
|
|
454
599
|
function first(metadata, keys) {
|
|
@@ -471,23 +616,20 @@ function findKey(value, key) {
|
|
|
471
616
|
function isRecord(value) {
|
|
472
617
|
return typeof value === "object" && value !== null;
|
|
473
618
|
}
|
|
474
|
-
|
|
475
|
-
//#region src/converters/zip.ts
|
|
619
|
+
// src/converters/zip.ts
|
|
476
620
|
var ZipConverter = class {
|
|
477
621
|
async convert(source, options = {}) {
|
|
478
|
-
if (options.
|
|
622
|
+
if (options.extension !== "zip") return null;
|
|
479
623
|
const files = unzipSync(source, { filter: (file) => !file.name.endsWith("/") });
|
|
480
|
-
const converters = options.
|
|
624
|
+
const converters = options._converters ?? [];
|
|
481
625
|
const sections = [`Content from the zip file \`${options.url ?? "archive.zip"}\`:`];
|
|
482
626
|
for (const [name, bytes] of Object.entries(files)) {
|
|
483
627
|
const result = await convertEntry(bytes, name, converters, options);
|
|
484
628
|
if (result) sections.push(`## File: ${name}\n\n${result.markdown}`);
|
|
485
629
|
}
|
|
486
|
-
const markdown = sections.join("\n\n");
|
|
487
630
|
return {
|
|
488
631
|
title: null,
|
|
489
|
-
markdown
|
|
490
|
-
text_content: markdown
|
|
632
|
+
markdown: sections.join("\n\n")
|
|
491
633
|
};
|
|
492
634
|
}
|
|
493
635
|
};
|
|
@@ -496,9 +638,9 @@ async function convertEntry(source, name, converters, options) {
|
|
|
496
638
|
if (!extension) return null;
|
|
497
639
|
const entryOptions = {
|
|
498
640
|
...options,
|
|
499
|
-
|
|
641
|
+
extension,
|
|
500
642
|
url: void 0,
|
|
501
|
-
|
|
643
|
+
_converters: converters
|
|
502
644
|
};
|
|
503
645
|
for (const converter of converters) try {
|
|
504
646
|
const result = await converter.convert(source, entryOptions);
|
|
@@ -506,41 +648,38 @@ async function convertEntry(source, name, converters, options) {
|
|
|
506
648
|
} catch {}
|
|
507
649
|
return null;
|
|
508
650
|
}
|
|
509
|
-
|
|
510
|
-
//#region src/markitdown.ts
|
|
651
|
+
// src/markitdown.ts
|
|
511
652
|
var MarkItDown = class {
|
|
653
|
+
converters = [];
|
|
512
654
|
constructor() {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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());
|
|
525
670
|
}
|
|
526
671
|
/** Convert a URL, response, blob, array buffer, data view, or typed array. */
|
|
527
672
|
async convert(source, options = {}) {
|
|
528
673
|
if (typeof source === "string" || source instanceof URL) return this.convertUrl(source.toString(), options);
|
|
529
674
|
if (isResponse(source)) return this.convertResponse(source, options);
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
return this.convertBytes(
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* @deprecated Pass the byte source directly to `convert`.
|
|
541
|
-
*/
|
|
542
|
-
async convertBuffer(source, options) {
|
|
543
|
-
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);
|
|
544
683
|
}
|
|
545
684
|
async convertUrl(source, { fetch: fetchImplementation = globalThis.fetch, ...options }) {
|
|
546
685
|
const url = new URL(source);
|
|
@@ -554,44 +693,39 @@ var MarkItDown = class {
|
|
|
554
693
|
});
|
|
555
694
|
}
|
|
556
695
|
async convertResponse(response, options) {
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
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, {
|
|
566
704
|
...options,
|
|
567
|
-
url:
|
|
705
|
+
url: options.url || response.url || void 0
|
|
568
706
|
});
|
|
569
707
|
}
|
|
570
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.");
|
|
571
710
|
let lastError;
|
|
572
711
|
for (const extension of extensions) for (const converter of this.converters) try {
|
|
573
712
|
const result = await converter.convert(source, {
|
|
574
713
|
...options,
|
|
575
|
-
|
|
576
|
-
|
|
714
|
+
extension,
|
|
715
|
+
_converters: this.converters
|
|
577
716
|
});
|
|
578
717
|
if (result) {
|
|
579
718
|
result.markdown = result.markdown.replace(/\r\n|\r|\n/g, "\n").trim().replace(/\n{3,}/g, "\n\n");
|
|
580
|
-
result.text_content = result.markdown;
|
|
581
719
|
return result;
|
|
582
720
|
}
|
|
583
721
|
} catch (error) {
|
|
584
722
|
if (options.debug) console.error("Converter threw error:", error);
|
|
585
723
|
lastError = error;
|
|
586
724
|
}
|
|
587
|
-
const formats =
|
|
725
|
+
const formats = extensions.join(", ");
|
|
588
726
|
if (lastError) throw new Error(`Could not convert ${formats} to Markdown: ${String(lastError)}`);
|
|
589
727
|
throw new Error(`Unsupported file type: ${formats}.`);
|
|
590
728
|
}
|
|
591
|
-
addExtension(extensions, extension) {
|
|
592
|
-
if (!extension) return;
|
|
593
|
-
extensions.add(extension.startsWith(".") ? extension.toLowerCase() : `.${extension.toLowerCase()}`);
|
|
594
|
-
}
|
|
595
729
|
registerConverter(converter) {
|
|
596
730
|
this.converters.unshift(converter);
|
|
597
731
|
}
|
|
@@ -599,13 +733,53 @@ var MarkItDown = class {
|
|
|
599
733
|
function isResponse(value) {
|
|
600
734
|
return typeof Response !== "undefined" && value instanceof Response;
|
|
601
735
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
})
|
|
610
|
-
|
|
611
|
-
|
|
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 };
|