dsh-plugin-lookatstudy 0.11.1 → 0.12.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 +0 -1
- package/lib/client.js +29 -98
- package/lib/client.js.map +1 -1
- package/lib/epub-parser-oH96guBW.mjs +306 -0
- package/lib/{html-article-C6nhqJbX.mjs → html-article-Da8ksU0i.mjs} +35 -1
- package/lib/index.mjs +56 -26
- package/lib/{pptx-parser-CD5pR2cj.mjs → pptx-parser-B9IDkiGM.mjs} +50 -2
- package/package.json +1 -1
- package/lib/epub-parser-DvlKap-d.mjs +0 -112
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { n as htmlToMarkdown } from "./html-article-C6nhqJbX.mjs";
|
|
2
|
-
import { n as readZipText, t as readZip } from "./zip-reader-KnRrq0av.mjs";
|
|
3
|
-
//#region src/vendor/epub-parser.ts
|
|
4
|
-
/** 提取某标签的全部出现(OPF 的属性顺序不定,先抓整标签再逐个提属性)。 */
|
|
5
|
-
function tags(xml, tagName) {
|
|
6
|
-
const out = [];
|
|
7
|
-
const re = new RegExp(`<${tagName}\\b[^>]*>`, "g");
|
|
8
|
-
let m;
|
|
9
|
-
while ((m = re.exec(xml)) !== null) {
|
|
10
|
-
const raw = m[0];
|
|
11
|
-
const attrs = {};
|
|
12
|
-
const attrRe = /([\w:-]+)\s*=\s*"([^"]*)"/g;
|
|
13
|
-
let a;
|
|
14
|
-
while ((a = attrRe.exec(raw)) !== null) attrs[a[1]] = a[2];
|
|
15
|
-
out.push({
|
|
16
|
-
raw,
|
|
17
|
-
attrs
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
return out;
|
|
21
|
-
}
|
|
22
|
-
function firstTagText(xml, tagName) {
|
|
23
|
-
return xml.match(new RegExp(`<${tagName}[^>]*>([^<]*)</${tagName}>`, "i"))?.[1]?.trim() ?? "";
|
|
24
|
-
}
|
|
25
|
-
/** zip 内路径归一:posix 分隔 + href 相对 OPF 目录解析。 */
|
|
26
|
-
function resolveZipPath(opfPath, href) {
|
|
27
|
-
const cleanHref = decodeURIComponent(href.split("#")[0] ?? href).replace(/\\/g, "/");
|
|
28
|
-
if (!opfPath.includes("/")) return cleanHref.replace(/^\.\//, "");
|
|
29
|
-
const parts = `${opfPath.slice(0, opfPath.lastIndexOf("/"))}/${cleanHref}`.split("/");
|
|
30
|
-
const resolved = [];
|
|
31
|
-
for (const p of parts) if (p === "..") resolved.pop();
|
|
32
|
-
else if (p !== "." && p !== "") resolved.push(p);
|
|
33
|
-
return resolved.join("/");
|
|
34
|
-
}
|
|
35
|
-
/** EPUB3 nav.xhtml 或 EPUB2 toc.ncx → href(去 fragment)→ 章节标题。 */
|
|
36
|
-
function parseTocLabels(tocXml, isNcx) {
|
|
37
|
-
const labels = /* @__PURE__ */ new Map();
|
|
38
|
-
if (isNcx) {
|
|
39
|
-
const blockRe = /<navPoint\b[\s\S]*?<\/navPoint>/g;
|
|
40
|
-
let m;
|
|
41
|
-
while ((m = blockRe.exec(tocXml)) !== null) {
|
|
42
|
-
const label = firstTagText(m[0], "text");
|
|
43
|
-
const src = m[0].match(/<content[^>]+src="([^"]+)"/i)?.[1];
|
|
44
|
-
if (label && src) labels.set(decodeURIComponent(src.split("#")[0].replace(/\\/g, "/")), label);
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
const aRe = /<a\b[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
48
|
-
let m;
|
|
49
|
-
while ((m = aRe.exec(tocXml)) !== null) {
|
|
50
|
-
const label = (m[2] ?? "").replace(/<[^>]+>/g, "").trim();
|
|
51
|
-
if (label) labels.set(decodeURIComponent((m[1] ?? "").split("#")[0].replace(/\\/g, "/")), label);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return labels;
|
|
55
|
-
}
|
|
56
|
-
function sanitizeFileName(title) {
|
|
57
|
-
return (title || "chapter").replace(/[\\/:*?"<>|#\s]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50) || "chapter";
|
|
58
|
-
}
|
|
59
|
-
function parseEpub(buf) {
|
|
60
|
-
const entries = readZip(buf);
|
|
61
|
-
const read = (p) => readZipText(entries, p);
|
|
62
|
-
const opfPath = read("META-INF/container.xml").match(/full-path="([^"]+)"/i)?.[1];
|
|
63
|
-
if (!opfPath) throw new Error("epub 结构异常:找不到 container.xml 里的 OPF 路径");
|
|
64
|
-
const opf = read(opfPath);
|
|
65
|
-
if (!opf) throw new Error(`epub 结构异常:OPF 文件缺失(${opfPath})`);
|
|
66
|
-
const bookTitle = firstTagText(opf, "dc:title") || "未命名电子书";
|
|
67
|
-
const manifest = /* @__PURE__ */ new Map();
|
|
68
|
-
for (const t of tags(opf, "item")) manifest.set(t.attrs["id"] ?? "", {
|
|
69
|
-
href: t.attrs["href"] ?? "",
|
|
70
|
-
mediaType: (t.attrs["media-type"] ?? "").toLowerCase(),
|
|
71
|
-
properties: t.attrs["properties"] ?? ""
|
|
72
|
-
});
|
|
73
|
-
const spineIds = tags(opf, "itemref").map((t) => t.attrs["idref"] ?? "").filter(Boolean);
|
|
74
|
-
const spineTocId = opf.match(/<spine\b[^>]*\btoc="([^"]+)"/i)?.[1];
|
|
75
|
-
let tocLabels = /* @__PURE__ */ new Map();
|
|
76
|
-
const navItem = [...manifest.values()].find((it) => it.properties.split(/\s+/).includes("nav"));
|
|
77
|
-
if (navItem?.href) tocLabels = parseTocLabels(read(resolveZipPath(opfPath, navItem.href)), false);
|
|
78
|
-
if (tocLabels.size === 0 && spineTocId && manifest.has(spineTocId)) tocLabels = parseTocLabels(read(resolveZipPath(opfPath, manifest.get(spineTocId).href)), true);
|
|
79
|
-
const opfDirKey = (href) => decodeURIComponent(href.split("#")[0] ?? href).replace(/\\/g, "/");
|
|
80
|
-
const chapters = [];
|
|
81
|
-
let n = 0;
|
|
82
|
-
for (const id of spineIds) {
|
|
83
|
-
const item = manifest.get(id);
|
|
84
|
-
if (!item?.href) continue;
|
|
85
|
-
if (!(item.mediaType === "application/xhtml+xml" || item.mediaType === "text/html" || /\.(xhtml|html|htm)$/i.test(item.href))) continue;
|
|
86
|
-
if (item.properties.split(/\s+/).includes("nav")) continue;
|
|
87
|
-
const xhtml = read(resolveZipPath(opfPath, item.href));
|
|
88
|
-
if (!xhtml) continue;
|
|
89
|
-
const md = htmlToMarkdown(xhtml, { stripImages: true });
|
|
90
|
-
if (!md || md.replace(/[#\s>*-]/g, "").length < 8) continue;
|
|
91
|
-
n++;
|
|
92
|
-
const body = md.startsWith("# ") && md.includes("\n") ? md.slice(md.indexOf("\n") + 1).trim() : md;
|
|
93
|
-
const firstHeading = md.startsWith("# ") ? md.split("\n")[0].slice(2).trim() : "";
|
|
94
|
-
const title = tocLabels.get(opfDirKey(item.href)) || firstHeading || `第 ${n} 章`;
|
|
95
|
-
chapters.push({
|
|
96
|
-
path: `chapters/${String(n).padStart(2, "0")}-${sanitizeFileName(title)}.md`,
|
|
97
|
-
title,
|
|
98
|
-
markdown: `# ${title}\n\n${body}`
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
if (chapters.length === 0) throw new Error("epub 里没有可识别的章节文本");
|
|
102
|
-
return {
|
|
103
|
-
title: bookTitle,
|
|
104
|
-
chapters
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
/** 文件夹导入路径用:整本书压平成一个 markdown(全部 H1 降为 H2,给结构设计当 anchor 拆章)。 */
|
|
108
|
-
function parseEpubFlat(buf) {
|
|
109
|
-
return parseEpub(buf).chapters.map((c) => c.markdown.replace(/^# /gm, "## ")).join("\n\n");
|
|
110
|
-
}
|
|
111
|
-
//#endregion
|
|
112
|
-
export { parseEpubFlat };
|