frond-js 0.4.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/LICENSE +21 -0
- package/README.en.md +247 -0
- package/README.md +211 -0
- package/dist/abort-BY8vBk0v.d.cts +99 -0
- package/dist/abort-BY8vBk0v.d.ts +99 -0
- package/dist/adapter-3G46J3CA.cjs +503 -0
- package/dist/adapter-3ONQWJVQ.js +501 -0
- package/dist/adapter-55QHWRSE.js +124 -0
- package/dist/adapter-DF34GBWJ.cjs +19 -0
- package/dist/adapter-EXTNILTC.cjs +126 -0
- package/dist/adapter-GOFC7TMC.js +284 -0
- package/dist/adapter-LRJTQ47I.cjs +286 -0
- package/dist/adapter-TWWZML4A.js +17 -0
- package/dist/adapter-ZM5FQTJT.js +1415 -0
- package/dist/adapter-ZRNSDQUV.cjs +1417 -0
- package/dist/chunk-2SUG7YFZ.cjs +108 -0
- package/dist/chunk-B2L2YVXD.js +89 -0
- package/dist/chunk-D4KWSEZD.js +393 -0
- package/dist/chunk-EZTIZO6R.cjs +430 -0
- package/dist/chunk-G7DLWGBW.cjs +103 -0
- package/dist/chunk-GTGLDLJD.cjs +479 -0
- package/dist/chunk-IIV6VIUJ.cjs +83 -0
- package/dist/chunk-JDTHZQUK.js +102 -0
- package/dist/chunk-JJVXT3AC.js +30 -0
- package/dist/chunk-LIXRYQL2.js +473 -0
- package/dist/chunk-NABYHI6X.cjs +400 -0
- package/dist/chunk-SJVOYNTF.js +425 -0
- package/dist/chunk-SLI2YL25.cjs +252 -0
- package/dist/chunk-U4264IQH.js +78 -0
- package/dist/chunk-UY2YRCFC.js +250 -0
- package/dist/chunk-WCZTTQ7Z.cjs +32 -0
- package/dist/core/index.cjs +162 -0
- package/dist/core/index.d.cts +321 -0
- package/dist/core/index.d.ts +321 -0
- package/dist/core/index.js +49 -0
- package/dist/default-DRLIJX73.js +1183 -0
- package/dist/default-UK52WOO5.cjs +1192 -0
- package/dist/formats/epub/index.cjs +29 -0
- package/dist/formats/epub/index.d.cts +286 -0
- package/dist/formats/epub/index.d.ts +286 -0
- package/dist/formats/epub/index.js +11 -0
- package/dist/index.cjs +655 -0
- package/dist/index.d.cts +335 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.js +600 -0
- package/dist/render/index.cjs +2 -0
- package/dist/render/index.d.cts +116 -0
- package/dist/render/index.d.ts +116 -0
- package/dist/render/index.js +1 -0
- package/dist/types-B76GOMxj.d.ts +129 -0
- package/dist/types-B7mslPBY.d.cts +166 -0
- package/dist/types-B7mslPBY.d.ts +166 -0
- package/dist/types-BH88rUYt.d.cts +129 -0
- package/dist/types-C-5eHSRH.d.ts +379 -0
- package/dist/types-CPUqTEPW.d.cts +379 -0
- package/dist/types-DQYmArgv.d.cts +17 -0
- package/dist/types-DQYmArgv.d.ts +17 -0
- package/package.json +115 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { resolveBookBytes } from './chunk-JJVXT3AC.js';
|
|
2
|
+
import { parseXml, findAll, getAttribute, findFirst, getText } from './chunk-D4KWSEZD.js';
|
|
3
|
+
import { parseZip } from './chunk-UY2YRCFC.js';
|
|
4
|
+
import { resolveArchivePath, directoryOf } from './chunk-U4264IQH.js';
|
|
5
|
+
import { throwIfAborted, isAbortError, ParseError, FormatError } from './chunk-B2L2YVXD.js';
|
|
6
|
+
|
|
7
|
+
// src/formats/epub/container.ts
|
|
8
|
+
var EPUB_PACKAGE_MEDIA_TYPE = "application/oebps-package+xml";
|
|
9
|
+
var DEFAULT_CONTAINER_PATH = "META-INF/container.xml";
|
|
10
|
+
function formatError(message, source) {
|
|
11
|
+
return new FormatError(`${message}\uFF08\u6E90: ${source}\uFF09`);
|
|
12
|
+
}
|
|
13
|
+
function toContainerError(error, source, containerPath) {
|
|
14
|
+
const detail = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
|
|
15
|
+
const message = `${detail}\uFF08container: ${containerPath}\uFF0C\u6E90: ${source}\uFF09`;
|
|
16
|
+
return error instanceof ParseError ? new ParseError(message, { cause: error }) : new FormatError(message, { cause: error });
|
|
17
|
+
}
|
|
18
|
+
async function parseContainer(archive, options = {}) {
|
|
19
|
+
const containerPath = options.containerPath ?? DEFAULT_CONTAINER_PATH;
|
|
20
|
+
const source = options.source ?? containerPath;
|
|
21
|
+
throwIfAborted(options.signal);
|
|
22
|
+
let xml;
|
|
23
|
+
try {
|
|
24
|
+
xml = await archive.readText(
|
|
25
|
+
containerPath,
|
|
26
|
+
options.signal === void 0 ? {} : { signal: options.signal }
|
|
27
|
+
);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (isAbortError(error)) throw error;
|
|
30
|
+
throw toContainerError(error, source, containerPath);
|
|
31
|
+
}
|
|
32
|
+
throwIfAborted(options.signal);
|
|
33
|
+
const document = parseXml(xml, { source });
|
|
34
|
+
for (const rootfile of findAll(document, "rootfile")) {
|
|
35
|
+
if (getAttribute(rootfile, "media-type") !== EPUB_PACKAGE_MEDIA_TYPE) continue;
|
|
36
|
+
const rawPath = getAttribute(rootfile, "full-path");
|
|
37
|
+
if (rawPath === void 0) {
|
|
38
|
+
throw formatError("rootfile \u7F3A\u5C11 full-path \u5C5E\u6027", source);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
opfPath: resolveArchivePath("", rawPath, {
|
|
42
|
+
label: "rootfile \u7684 full-path",
|
|
43
|
+
source
|
|
44
|
+
}),
|
|
45
|
+
mediaType: EPUB_PACKAGE_MEDIA_TYPE
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
throw formatError(
|
|
49
|
+
`container \u4E2D\u6CA1\u6709\u53D7\u652F\u6301\u7684 package rootfile\uFF08\u671F\u671B media-type="${EPUB_PACKAGE_MEDIA_TYPE}"\uFF09`,
|
|
50
|
+
source
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/formats/epub/opf.ts
|
|
55
|
+
var PACKAGE_LOCAL_NAME = "package";
|
|
56
|
+
function formatError2(message, source) {
|
|
57
|
+
return new FormatError(`${message}\uFF08\u6E90: ${source}\uFF09`);
|
|
58
|
+
}
|
|
59
|
+
function toOpfError(error, source, opfPath) {
|
|
60
|
+
const detail = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
|
|
61
|
+
const message = `${detail}\uFF08OPF: ${opfPath}\uFF0C\u6E90: ${source}\uFF09`;
|
|
62
|
+
return error instanceof ParseError ? new ParseError(message, { cause: error }) : new FormatError(message, { cause: error });
|
|
63
|
+
}
|
|
64
|
+
function textOf(element) {
|
|
65
|
+
return getText(element).trim();
|
|
66
|
+
}
|
|
67
|
+
function firstNonEmptyText(root, localName) {
|
|
68
|
+
for (const element of findAll(root, localName)) {
|
|
69
|
+
const text = textOf(element);
|
|
70
|
+
if (text !== "") return text;
|
|
71
|
+
}
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
function stripFragment(href) {
|
|
75
|
+
const index = href.indexOf("#");
|
|
76
|
+
return index === -1 ? href : href.slice(0, index);
|
|
77
|
+
}
|
|
78
|
+
function resolveIdentifier(metadataElement, packageElement, source) {
|
|
79
|
+
const identifiers = findAll(metadataElement, "identifier");
|
|
80
|
+
if (identifiers.length === 0) {
|
|
81
|
+
throw formatError2("\u7F3A\u5C11\u5FC5\u9700\u7684 dc:identifier", source);
|
|
82
|
+
}
|
|
83
|
+
const uniqueId = getAttribute(packageElement, "unique-identifier");
|
|
84
|
+
if (uniqueId !== void 0 && uniqueId !== "") {
|
|
85
|
+
const matched = identifiers.find((element) => getAttribute(element, "id") === uniqueId);
|
|
86
|
+
if (matched === void 0) {
|
|
87
|
+
throw formatError2(
|
|
88
|
+
`package@unique-identifier \u6307\u5411\u4E0D\u5B58\u5728\u7684 dc:identifier\uFF08id="${uniqueId}"\uFF09`,
|
|
89
|
+
source
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const text = textOf(matched);
|
|
93
|
+
if (text === "") {
|
|
94
|
+
throw formatError2(`dc:identifier\uFF08id="${uniqueId}"\uFF09\u4E3A\u7A7A`, source);
|
|
95
|
+
}
|
|
96
|
+
return text;
|
|
97
|
+
}
|
|
98
|
+
const fallback = identifiers.map(textOf).find((text) => text !== "");
|
|
99
|
+
if (fallback === void 0) {
|
|
100
|
+
throw formatError2("\u7F3A\u5C11\u5FC5\u9700\u7684 dc:identifier", source);
|
|
101
|
+
}
|
|
102
|
+
return fallback;
|
|
103
|
+
}
|
|
104
|
+
function resolveModified(metadataElement) {
|
|
105
|
+
for (const element of findAll(metadataElement, "meta")) {
|
|
106
|
+
if (getAttribute(element, "property") !== "dcterms:modified") continue;
|
|
107
|
+
const text = textOf(element);
|
|
108
|
+
if (text !== "") return text;
|
|
109
|
+
}
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
function buildMetadata(metadataElement, packageElement, source) {
|
|
113
|
+
const title = firstNonEmptyText(metadataElement, "title");
|
|
114
|
+
if (title === void 0) {
|
|
115
|
+
throw formatError2("\u7F3A\u5C11\u5FC5\u9700\u7684 dc:title", source);
|
|
116
|
+
}
|
|
117
|
+
const language = firstNonEmptyText(metadataElement, "language");
|
|
118
|
+
if (language === void 0) {
|
|
119
|
+
throw formatError2("\u7F3A\u5C11\u5FC5\u9700\u7684 dc:language", source);
|
|
120
|
+
}
|
|
121
|
+
const creators = findAll(metadataElement, "creator").map(textOf).filter((text) => text !== "");
|
|
122
|
+
const identifier = resolveIdentifier(metadataElement, packageElement, source);
|
|
123
|
+
const modified = resolveModified(metadataElement);
|
|
124
|
+
return {
|
|
125
|
+
title,
|
|
126
|
+
creators,
|
|
127
|
+
language,
|
|
128
|
+
identifier,
|
|
129
|
+
...modified === void 0 ? {} : { modified }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function buildManifest(manifestElement, basePath, source, signal) {
|
|
133
|
+
const items = [];
|
|
134
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
135
|
+
for (const element of findAll(manifestElement, "item")) {
|
|
136
|
+
throwIfAborted(signal);
|
|
137
|
+
const id = getAttribute(element, "id");
|
|
138
|
+
if (id === void 0 || id === "") {
|
|
139
|
+
throw formatError2("manifest \u7684 <item> \u7F3A\u5C11 id \u5C5E\u6027", source);
|
|
140
|
+
}
|
|
141
|
+
if (seenIds.has(id)) {
|
|
142
|
+
throw formatError2(`manifest \u4E2D\u5B58\u5728\u91CD\u590D id="${id}"`, source);
|
|
143
|
+
}
|
|
144
|
+
seenIds.add(id);
|
|
145
|
+
const rawHref = getAttribute(element, "href");
|
|
146
|
+
if (rawHref === void 0 || rawHref === "") {
|
|
147
|
+
throw formatError2(`manifest item "${id}" \u7F3A\u5C11 href \u5C5E\u6027`, source);
|
|
148
|
+
}
|
|
149
|
+
const mediaType = getAttribute(element, "media-type");
|
|
150
|
+
if (mediaType === void 0 || mediaType === "") {
|
|
151
|
+
throw formatError2(`manifest item "${id}" \u7F3A\u5C11 media-type \u5C5E\u6027`, source);
|
|
152
|
+
}
|
|
153
|
+
const properties = (getAttribute(element, "properties") ?? "").split(/\s+/).filter((property) => property !== "");
|
|
154
|
+
const href = resolveArchivePath(basePath, stripFragment(rawHref), {
|
|
155
|
+
label: `manifest item "${id}" \u7684 href`,
|
|
156
|
+
source
|
|
157
|
+
});
|
|
158
|
+
items.push({ id, href, mediaType, properties });
|
|
159
|
+
}
|
|
160
|
+
return items;
|
|
161
|
+
}
|
|
162
|
+
function buildSpine(spineElement, manifestIndex, source, signal) {
|
|
163
|
+
const spine = [];
|
|
164
|
+
let index = 0;
|
|
165
|
+
for (const element of findAll(spineElement, "itemref")) {
|
|
166
|
+
throwIfAborted(signal);
|
|
167
|
+
const idref = getAttribute(element, "idref");
|
|
168
|
+
if (idref === void 0 || idref === "") {
|
|
169
|
+
throw formatError2("spine \u7684 <itemref> \u7F3A\u5C11 idref \u5C5E\u6027", source);
|
|
170
|
+
}
|
|
171
|
+
const item = manifestIndex.get(idref);
|
|
172
|
+
if (item === void 0) {
|
|
173
|
+
throw formatError2(`spine \u7684 <itemref> \u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684 manifest id="${idref}"`, source);
|
|
174
|
+
}
|
|
175
|
+
const linear = getAttribute(element, "linear") !== "no";
|
|
176
|
+
spine.push({ idref, linear, index, item });
|
|
177
|
+
index += 1;
|
|
178
|
+
}
|
|
179
|
+
return spine;
|
|
180
|
+
}
|
|
181
|
+
function resolveNcxPath(spineElement, manifestIndex) {
|
|
182
|
+
const tocId = getAttribute(spineElement, "toc");
|
|
183
|
+
if (tocId === void 0 || tocId === "") return void 0;
|
|
184
|
+
return manifestIndex.get(tocId)?.href;
|
|
185
|
+
}
|
|
186
|
+
function resolveDirection(spineElement) {
|
|
187
|
+
const raw = getAttribute(spineElement, "page-progression-direction")?.trim().toLowerCase();
|
|
188
|
+
return raw === "ltr" || raw === "rtl" ? raw : void 0;
|
|
189
|
+
}
|
|
190
|
+
function buildBook(document, opfPath, source, signal) {
|
|
191
|
+
if (document.localName !== PACKAGE_LOCAL_NAME) {
|
|
192
|
+
throw formatError2(`OPF \u6839\u5143\u7D20\u5E94\u4E3A <package>\uFF0C\u5B9E\u9645\u4E3A <${document.name}>`, source);
|
|
193
|
+
}
|
|
194
|
+
const metadataElement = findFirst(document, "metadata");
|
|
195
|
+
if (metadataElement === void 0) {
|
|
196
|
+
throw formatError2("\u7F3A\u5C11 <metadata> \u5143\u7D20", source);
|
|
197
|
+
}
|
|
198
|
+
const manifestElement = findFirst(document, "manifest");
|
|
199
|
+
if (manifestElement === void 0) {
|
|
200
|
+
throw formatError2("\u7F3A\u5C11 <manifest> \u5143\u7D20", source);
|
|
201
|
+
}
|
|
202
|
+
const spineElement = findFirst(document, "spine");
|
|
203
|
+
if (spineElement === void 0) {
|
|
204
|
+
throw formatError2("\u7F3A\u5C11 <spine> \u5143\u7D20", source);
|
|
205
|
+
}
|
|
206
|
+
const basePath = directoryOf(opfPath);
|
|
207
|
+
const metadata = buildMetadata(metadataElement, document, source);
|
|
208
|
+
const manifest = buildManifest(manifestElement, basePath, source, signal);
|
|
209
|
+
const manifestIndex = new Map(manifest.map((item) => [item.id, item]));
|
|
210
|
+
const spine = buildSpine(spineElement, manifestIndex, source, signal);
|
|
211
|
+
const ncxPath = resolveNcxPath(spineElement, manifestIndex);
|
|
212
|
+
const direction = resolveDirection(spineElement);
|
|
213
|
+
const tocItem = manifest.find((item) => item.properties.includes("nav"));
|
|
214
|
+
const resources = manifest.map(({ href, mediaType }) => ({ href, mediaType }));
|
|
215
|
+
const chapters = spine.map(({ item, linear }) => ({
|
|
216
|
+
href: item.href,
|
|
217
|
+
mediaType: item.mediaType,
|
|
218
|
+
linear
|
|
219
|
+
}));
|
|
220
|
+
return {
|
|
221
|
+
format: "epub",
|
|
222
|
+
metadata,
|
|
223
|
+
resources,
|
|
224
|
+
chapters,
|
|
225
|
+
opfPath,
|
|
226
|
+
basePath,
|
|
227
|
+
manifest,
|
|
228
|
+
spine,
|
|
229
|
+
manifestIndex,
|
|
230
|
+
// exactOptionalPropertyTypes:可选字段不能显式传 `undefined`。
|
|
231
|
+
...tocItem === void 0 ? {} : { tocItem },
|
|
232
|
+
...ncxPath === void 0 ? {} : { ncxPath },
|
|
233
|
+
// 同上。`direction` 在「缺省 / `default` / 非法值」三种情况下都缺席(见 `resolveDirection`)。
|
|
234
|
+
...direction === void 0 ? {} : { direction }
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function parseOpf(archive, opfPath, options = {}) {
|
|
238
|
+
const source = options.source ?? opfPath;
|
|
239
|
+
throwIfAborted(options.signal);
|
|
240
|
+
let xml;
|
|
241
|
+
try {
|
|
242
|
+
xml = await archive.readText(
|
|
243
|
+
opfPath,
|
|
244
|
+
options.signal === void 0 ? {} : { signal: options.signal }
|
|
245
|
+
);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (isAbortError(error)) throw error;
|
|
248
|
+
throw toOpfError(error, source, opfPath);
|
|
249
|
+
}
|
|
250
|
+
throwIfAborted(options.signal);
|
|
251
|
+
const document = parseXml(xml, { source });
|
|
252
|
+
return buildBook(document, opfPath, source, options.signal);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/formats/epub/nav.ts
|
|
256
|
+
function formatError3(message, source) {
|
|
257
|
+
return new FormatError(`${message}\uFF08\u6E90: ${source}\uFF09`);
|
|
258
|
+
}
|
|
259
|
+
function toTocError(error, source, documentPath) {
|
|
260
|
+
const detail = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
|
|
261
|
+
const message = `${detail}\uFF08\u76EE\u5F55: ${documentPath}\uFF0C\u6E90: ${source}\uFF09`;
|
|
262
|
+
return error instanceof ParseError ? new ParseError(message, { cause: error }) : new FormatError(message, { cause: error });
|
|
263
|
+
}
|
|
264
|
+
function directChildren(element, localName) {
|
|
265
|
+
const matched = [];
|
|
266
|
+
for (const child of element.children) {
|
|
267
|
+
if (child.type !== "element") continue;
|
|
268
|
+
if (child.localName === localName) matched.push(child);
|
|
269
|
+
}
|
|
270
|
+
return matched;
|
|
271
|
+
}
|
|
272
|
+
function firstDirectChild(element, localName) {
|
|
273
|
+
for (const child of element.children) {
|
|
274
|
+
if (child.type === "element" && child.localName === localName) return child;
|
|
275
|
+
}
|
|
276
|
+
return void 0;
|
|
277
|
+
}
|
|
278
|
+
function typeTokens(element) {
|
|
279
|
+
for (const [name, value] of element.attributes) {
|
|
280
|
+
if (name === "epub:type" || name.endsWith(":type")) {
|
|
281
|
+
return value.split(/\s+/).filter((token) => token !== "");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
function textOf2(element) {
|
|
287
|
+
return getText(element).trim();
|
|
288
|
+
}
|
|
289
|
+
function splitReference(raw) {
|
|
290
|
+
const index = raw.indexOf("#");
|
|
291
|
+
if (index === -1) return { path: raw };
|
|
292
|
+
const fragment = raw.slice(index + 1);
|
|
293
|
+
return { path: raw.slice(0, index), ...fragment === "" ? {} : { fragment } };
|
|
294
|
+
}
|
|
295
|
+
function resolveTarget(raw, context, label) {
|
|
296
|
+
const { path, fragment } = splitReference(raw);
|
|
297
|
+
const href = path === "" ? context.documentPath : resolveArchivePath(directoryOf(context.documentPath), path, {
|
|
298
|
+
label,
|
|
299
|
+
source: context.source
|
|
300
|
+
});
|
|
301
|
+
return { href, ...fragment === void 0 ? {} : { fragment } };
|
|
302
|
+
}
|
|
303
|
+
function buildItem(label, target, children) {
|
|
304
|
+
return {
|
|
305
|
+
label,
|
|
306
|
+
href: target.href,
|
|
307
|
+
...target.fragment === void 0 ? {} : { fragment: target.fragment },
|
|
308
|
+
children
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function selectTocNav(document) {
|
|
312
|
+
const navs = findAll(document, "nav");
|
|
313
|
+
if (navs.length === 0) return void 0;
|
|
314
|
+
return navs.find((nav) => typeTokens(nav).includes("toc")) ?? navs[0];
|
|
315
|
+
}
|
|
316
|
+
function parseNavList(olElement, context) {
|
|
317
|
+
const items = [];
|
|
318
|
+
for (const li of directChildren(olElement, "li")) {
|
|
319
|
+
throwIfAborted(context.signal);
|
|
320
|
+
items.push(parseNavItem(li, context));
|
|
321
|
+
}
|
|
322
|
+
return items;
|
|
323
|
+
}
|
|
324
|
+
function parseNavItem(li, context) {
|
|
325
|
+
const anchor = firstDirectChild(li, "a");
|
|
326
|
+
if (anchor === void 0) {
|
|
327
|
+
const span = firstDirectChild(li, "span");
|
|
328
|
+
throw formatError3(
|
|
329
|
+
span === void 0 ? "\u76EE\u5F55\u9879\u7684 <li> \u5185\u7F3A\u5C11 <a> \u5143\u7D20" : "\u76EE\u5F55\u9879\u53EA\u6709 <span>\uFF08\u65E0\u76EE\u6807\u94FE\u63A5\uFF09\uFF0C\u672C\u5B9E\u73B0\u8981\u6C42\u76EE\u5F55\u9879\u5FC5\u987B\u6307\u5411\u5177\u4F53\u6587\u6863",
|
|
330
|
+
context.source
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const label = textOf2(anchor);
|
|
334
|
+
const rawHref = getAttribute(anchor, "href");
|
|
335
|
+
if (rawHref === void 0 || rawHref === "") {
|
|
336
|
+
throw formatError3(`\u76EE\u5F55\u9879\u7684 <a> \u7F3A\u5C11 href \u5C5E\u6027\uFF08label: "${label}"\uFF09`, context.source);
|
|
337
|
+
}
|
|
338
|
+
const target = resolveTarget(rawHref, context, `\u76EE\u5F55\u9879 "${label}" \u7684 href`);
|
|
339
|
+
const children = [];
|
|
340
|
+
for (const nested of directChildren(li, "ol")) {
|
|
341
|
+
children.push(...parseNavList(nested, context));
|
|
342
|
+
}
|
|
343
|
+
return buildItem(label, target, children);
|
|
344
|
+
}
|
|
345
|
+
function parseNavDocument(document, context) {
|
|
346
|
+
const nav = selectTocNav(document);
|
|
347
|
+
if (nav === void 0) return [];
|
|
348
|
+
const items = [];
|
|
349
|
+
for (const ol of directChildren(nav, "ol")) {
|
|
350
|
+
items.push(...parseNavList(ol, context));
|
|
351
|
+
}
|
|
352
|
+
return items;
|
|
353
|
+
}
|
|
354
|
+
function parseNcxPoints(points, context) {
|
|
355
|
+
const items = [];
|
|
356
|
+
for (const point of points) {
|
|
357
|
+
throwIfAborted(context.signal);
|
|
358
|
+
const labelElement = firstDirectChild(point, "navLabel");
|
|
359
|
+
const label = labelElement === void 0 ? "" : textOf2(labelElement);
|
|
360
|
+
const content = firstDirectChild(point, "content");
|
|
361
|
+
const rawSrc = content === void 0 ? void 0 : getAttribute(content, "src");
|
|
362
|
+
if (rawSrc === void 0 || rawSrc === "") {
|
|
363
|
+
throw formatError3(`navPoint \u7F3A\u5C11 content@src\uFF08label: "${label}"\uFF09`, context.source);
|
|
364
|
+
}
|
|
365
|
+
const target = resolveTarget(rawSrc, context, `navPoint "${label}" \u7684 content@src`);
|
|
366
|
+
const children = parseNcxPoints(directChildren(point, "navPoint"), context);
|
|
367
|
+
items.push(buildItem(label, target, children));
|
|
368
|
+
}
|
|
369
|
+
return items;
|
|
370
|
+
}
|
|
371
|
+
function parseNcxDocument(document, context) {
|
|
372
|
+
const navMap = firstDirectChild(document, "navMap");
|
|
373
|
+
if (navMap === void 0) return [];
|
|
374
|
+
return parseNcxPoints(directChildren(navMap, "navPoint"), context);
|
|
375
|
+
}
|
|
376
|
+
async function readAndParse(archive, documentPath, context, parse) {
|
|
377
|
+
throwIfAborted(context.signal);
|
|
378
|
+
let xml;
|
|
379
|
+
try {
|
|
380
|
+
xml = await archive.readText(
|
|
381
|
+
documentPath,
|
|
382
|
+
context.signal === void 0 ? {} : { signal: context.signal }
|
|
383
|
+
);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (isAbortError(error)) throw error;
|
|
386
|
+
throw toTocError(error, context.source, documentPath);
|
|
387
|
+
}
|
|
388
|
+
throwIfAborted(context.signal);
|
|
389
|
+
return parse(parseXml(xml, { source: context.source }), context);
|
|
390
|
+
}
|
|
391
|
+
async function parseToc(archive, book, options = {}) {
|
|
392
|
+
const navItem = book.tocItem;
|
|
393
|
+
if (navItem !== void 0) {
|
|
394
|
+
const context = {
|
|
395
|
+
documentPath: navItem.href,
|
|
396
|
+
source: options.source ?? navItem.href,
|
|
397
|
+
signal: options.signal
|
|
398
|
+
};
|
|
399
|
+
return readAndParse(archive, navItem.href, context, parseNavDocument);
|
|
400
|
+
}
|
|
401
|
+
const ncxPath = book.ncxPath;
|
|
402
|
+
if (ncxPath !== void 0) {
|
|
403
|
+
const context = {
|
|
404
|
+
documentPath: ncxPath,
|
|
405
|
+
source: options.source ?? ncxPath,
|
|
406
|
+
signal: options.signal
|
|
407
|
+
};
|
|
408
|
+
return readAndParse(archive, ncxPath, context, parseNcxDocument);
|
|
409
|
+
}
|
|
410
|
+
throwIfAborted(options.signal);
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/formats/epub/open.ts
|
|
415
|
+
async function openBook(source, options = {}) {
|
|
416
|
+
throwIfAborted(options.signal);
|
|
417
|
+
const bytes = await resolveBookBytes(source, options);
|
|
418
|
+
const archive = parseZip(bytes, options);
|
|
419
|
+
const container = await parseContainer(archive, options);
|
|
420
|
+
const book = await parseOpf(archive, container.opfPath, options);
|
|
421
|
+
const toc = await parseToc(archive, book, options);
|
|
422
|
+
return { archive, book, toc };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export { openBook, parseContainer, parseOpf, parseToc };
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkIIV6VIUJ_cjs = require('./chunk-IIV6VIUJ.cjs');
|
|
4
|
+
var chunkG7DLWGBW_cjs = require('./chunk-G7DLWGBW.cjs');
|
|
5
|
+
|
|
6
|
+
// src/core/zip/inflate.ts
|
|
7
|
+
var CRC_TABLE = (() => {
|
|
8
|
+
const table = new Uint32Array(256);
|
|
9
|
+
for (let index = 0; index < 256; index += 1) {
|
|
10
|
+
let value = index;
|
|
11
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
12
|
+
value = (value & 1) !== 0 ? 3988292384 ^ value >>> 1 : value >>> 1;
|
|
13
|
+
}
|
|
14
|
+
table[index] = value >>> 0;
|
|
15
|
+
}
|
|
16
|
+
return new DataView(table.buffer);
|
|
17
|
+
})();
|
|
18
|
+
function crc32(data) {
|
|
19
|
+
let crc = 4294967295;
|
|
20
|
+
for (const byte of data) {
|
|
21
|
+
crc = crc >>> 8 ^ CRC_TABLE.getUint32(((crc ^ byte) & 255) << 2, true);
|
|
22
|
+
}
|
|
23
|
+
return (crc ^ 4294967295) >>> 0;
|
|
24
|
+
}
|
|
25
|
+
async function inflateRaw(data, label, maxOutputBytes) {
|
|
26
|
+
const chunks = [];
|
|
27
|
+
let total = 0;
|
|
28
|
+
try {
|
|
29
|
+
const view = data.buffer instanceof ArrayBuffer ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
|
|
30
|
+
const stream = new Blob([view]).stream().pipeThrough(new DecompressionStream("deflate-raw"));
|
|
31
|
+
const reader = stream.getReader();
|
|
32
|
+
for (; ; ) {
|
|
33
|
+
const { done, value } = await reader.read();
|
|
34
|
+
if (done) break;
|
|
35
|
+
total += value.byteLength;
|
|
36
|
+
if (total > maxOutputBytes) {
|
|
37
|
+
await reader.cancel();
|
|
38
|
+
throw new chunkG7DLWGBW_cjs.ParseError(
|
|
39
|
+
`deflate \u89E3\u538B\u4EA7\u7269\u8D85\u8FC7\u4E0A\u9650 ${String(maxOutputBytes)} \u5B57\u8282\uFF08\u6E90: ${label}\uFF09\uFF0C\u5DF2\u4EA7\u51FA ${String(total)} \u5B57\u8282\u5373\u4E2D\u6B62`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
chunks.push(value);
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (chunkG7DLWGBW_cjs.isFrondError(error)) throw error;
|
|
46
|
+
throw new chunkG7DLWGBW_cjs.ParseError(`deflate \u89E3\u538B\u5931\u8D25\uFF08\u6E90: ${label}\uFF09`, { cause: error });
|
|
47
|
+
}
|
|
48
|
+
const out = new Uint8Array(total);
|
|
49
|
+
let pos = 0;
|
|
50
|
+
for (const chunk of chunks) {
|
|
51
|
+
out.set(chunk, pos);
|
|
52
|
+
pos += chunk.byteLength;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/core/zip/parse.ts
|
|
58
|
+
var SIGNATURE_EOCD = 101010256;
|
|
59
|
+
var SIGNATURE_CENTRAL = 33639248;
|
|
60
|
+
var SIGNATURE_LOCAL = 67324752;
|
|
61
|
+
var EOCD_SIZE = 22;
|
|
62
|
+
var EOCD_MAX_COMMENT = 65535;
|
|
63
|
+
var CENTRAL_HEADER_SIZE = 46;
|
|
64
|
+
var LOCAL_HEADER_SIZE = 30;
|
|
65
|
+
var ZIP64_EXTRA_ID = 1;
|
|
66
|
+
var FLAG_ENCRYPTED = 1;
|
|
67
|
+
var METHOD_STORED = 0;
|
|
68
|
+
var METHOD_DEFLATE = 8;
|
|
69
|
+
var SENTINEL_16 = 65535;
|
|
70
|
+
var SENTINEL_32 = 4294967295;
|
|
71
|
+
var DEFAULT_MAX_ENTRY_SIZE_BYTES = 64 * 1024 * 1024;
|
|
72
|
+
var textDecoder = new TextDecoder("utf-8");
|
|
73
|
+
function fail(reason, offset, label) {
|
|
74
|
+
throw new chunkG7DLWGBW_cjs.ParseError(`ZIP \u89E3\u6790\u5931\u8D25\uFF1A${reason}\uFF08\u6E90: ${label}\uFF0C\u504F\u79FB ${String(offset)}\uFF09`);
|
|
75
|
+
}
|
|
76
|
+
function formatFail(reason, label) {
|
|
77
|
+
throw new chunkG7DLWGBW_cjs.FormatError(`ZIP \u683C\u5F0F\u4E0D\u53D7\u652F\u6301\uFF1A${reason}\uFF08\u6E90: ${label}\uFF09`);
|
|
78
|
+
}
|
|
79
|
+
function toMethod(value, name, label) {
|
|
80
|
+
if (value === METHOD_STORED) return METHOD_STORED;
|
|
81
|
+
if (value === METHOD_DEFLATE) return METHOD_DEFLATE;
|
|
82
|
+
return formatFail(`\u4E0D\u652F\u6301\u7684\u538B\u7F29\u65B9\u6CD5 ${String(value)}\uFF0C\u6761\u76EE "${name}"`, label);
|
|
83
|
+
}
|
|
84
|
+
function hasZip64Extra(view, offset, length) {
|
|
85
|
+
let cursor = offset;
|
|
86
|
+
const end = offset + length;
|
|
87
|
+
while (cursor + 4 <= end) {
|
|
88
|
+
if (view.getUint16(cursor, true) === ZIP64_EXTRA_ID) return true;
|
|
89
|
+
cursor += 4 + view.getUint16(cursor + 2, true);
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function canonicalEntryName(rawName, label) {
|
|
94
|
+
return chunkIIV6VIUJ_cjs.normalizeArchivePath(rawName, { source: label, label: "\u6761\u76EE\u540D" });
|
|
95
|
+
}
|
|
96
|
+
function lookupKey(name) {
|
|
97
|
+
try {
|
|
98
|
+
return chunkIIV6VIUJ_cjs.normalizeArchivePath(name);
|
|
99
|
+
} catch {
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function findEocd(view, length, label) {
|
|
104
|
+
if (length < EOCD_SIZE) fail("\u6587\u4EF6\u8FC7\u5C0F\uFF0C\u4E0D\u53EF\u80FD\u662F ZIP", length, label);
|
|
105
|
+
const floor = Math.max(0, length - EOCD_SIZE - EOCD_MAX_COMMENT);
|
|
106
|
+
for (let index = length - EOCD_SIZE; index >= floor; index -= 1) {
|
|
107
|
+
if (view.getUint32(index, true) !== SIGNATURE_EOCD) continue;
|
|
108
|
+
const commentLength = view.getUint16(index + 20, true);
|
|
109
|
+
if (index + EOCD_SIZE + commentLength === length) return index;
|
|
110
|
+
}
|
|
111
|
+
return fail("\u672A\u627E\u5230 EOCD \u8BB0\u5F55", length, label);
|
|
112
|
+
}
|
|
113
|
+
function readCentralDirectory(bytes, view, eocd, label) {
|
|
114
|
+
const totalEntries = view.getUint16(eocd + 10, true);
|
|
115
|
+
const centralSize = view.getUint32(eocd + 12, true);
|
|
116
|
+
const centralOffset = view.getUint32(eocd + 16, true);
|
|
117
|
+
if (totalEntries === SENTINEL_16 || centralSize === SENTINEL_32 || centralOffset === SENTINEL_32) {
|
|
118
|
+
return formatFail("\u5F52\u6863\u4E3A ZIP64\uFF0C\u4E0D\u53D7\u652F\u6301", label);
|
|
119
|
+
}
|
|
120
|
+
if (centralOffset + centralSize > eocd) {
|
|
121
|
+
fail("\u4E2D\u592E\u76EE\u5F55\u8303\u56F4\u8D8A\u754C", centralOffset, label);
|
|
122
|
+
}
|
|
123
|
+
const records = [];
|
|
124
|
+
const foldedFrom = /* @__PURE__ */ new Map();
|
|
125
|
+
let cursor = centralOffset;
|
|
126
|
+
for (let index = 0; index < totalEntries; index += 1) {
|
|
127
|
+
if (cursor + CENTRAL_HEADER_SIZE > bytes.length) fail("\u4E2D\u592E\u76EE\u5F55\u88AB\u622A\u65AD", cursor, label);
|
|
128
|
+
if (view.getUint32(cursor, true) !== SIGNATURE_CENTRAL) {
|
|
129
|
+
fail("\u4E2D\u592E\u76EE\u5F55\u5934\u7B7E\u540D\u65E0\u6548", cursor, label);
|
|
130
|
+
}
|
|
131
|
+
const flags = view.getUint16(cursor + 8, true);
|
|
132
|
+
const rawMethod = view.getUint16(cursor + 10, true);
|
|
133
|
+
const crc = view.getUint32(cursor + 16, true);
|
|
134
|
+
const compressedSize = view.getUint32(cursor + 20, true);
|
|
135
|
+
const uncompressedSize = view.getUint32(cursor + 24, true);
|
|
136
|
+
const nameLength = view.getUint16(cursor + 28, true);
|
|
137
|
+
const extraLength = view.getUint16(cursor + 30, true);
|
|
138
|
+
const commentLength = view.getUint16(cursor + 32, true);
|
|
139
|
+
const localOffset = view.getUint32(cursor + 42, true);
|
|
140
|
+
const nameStart = cursor + CENTRAL_HEADER_SIZE;
|
|
141
|
+
const nameEnd = nameStart + nameLength;
|
|
142
|
+
if (nameEnd > bytes.length) fail("\u6761\u76EE\u540D\u8D8A\u754C", nameStart, label);
|
|
143
|
+
if (nameEnd + extraLength + commentLength > bytes.length) {
|
|
144
|
+
fail("\u4E2D\u592E\u76EE\u5F55\u8BB0\u5F55\u957F\u5EA6\u8D8A\u754C", cursor, label);
|
|
145
|
+
}
|
|
146
|
+
const rawName = textDecoder.decode(bytes.subarray(nameStart, nameEnd)).replace(/\\/g, "/");
|
|
147
|
+
const isDirectory = rawName.endsWith("/");
|
|
148
|
+
const canonical = canonicalEntryName(rawName, label);
|
|
149
|
+
const previous = foldedFrom.get(canonical);
|
|
150
|
+
if (previous !== void 0 && previous !== rawName) {
|
|
151
|
+
throw new chunkG7DLWGBW_cjs.FormatError(
|
|
152
|
+
`\u6761\u76EE\u540D\u5F52\u4E00\u5316\u540E\u6B67\u4E49\uFF1A"${previous}" \u4E0E "${rawName}" \u90FD\u6307\u5411 "${canonical}"\uFF08\u6E90: ${label}\uFF09`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
foldedFrom.set(canonical, rawName);
|
|
156
|
+
const name = isDirectory ? `${canonical}/` : canonical;
|
|
157
|
+
if ((flags & FLAG_ENCRYPTED) !== 0) {
|
|
158
|
+
formatFail(`\u6761\u76EE "${name}" \u5DF2\u52A0\u5BC6`, label);
|
|
159
|
+
}
|
|
160
|
+
if (hasZip64Extra(view, nameEnd, extraLength)) {
|
|
161
|
+
formatFail(`\u6761\u76EE "${name}" \u4F7F\u7528 ZIP64 \u6269\u5C55\u4FE1\u606F`, label);
|
|
162
|
+
}
|
|
163
|
+
records.push({
|
|
164
|
+
entry: {
|
|
165
|
+
name,
|
|
166
|
+
method: toMethod(rawMethod, name, label),
|
|
167
|
+
compressedSize,
|
|
168
|
+
uncompressedSize,
|
|
169
|
+
crc32: crc,
|
|
170
|
+
isDirectory
|
|
171
|
+
},
|
|
172
|
+
localOffset
|
|
173
|
+
});
|
|
174
|
+
cursor = nameEnd + extraLength + commentLength;
|
|
175
|
+
}
|
|
176
|
+
return records;
|
|
177
|
+
}
|
|
178
|
+
async function readEntry(bytes, record, label, maxEntrySizeBytes) {
|
|
179
|
+
const { entry, localOffset } = record;
|
|
180
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
181
|
+
if (localOffset + LOCAL_HEADER_SIZE > bytes.length) fail("local header \u8D8A\u754C", localOffset, label);
|
|
182
|
+
if (view.getUint32(localOffset, true) !== SIGNATURE_LOCAL) {
|
|
183
|
+
fail("local header \u7B7E\u540D\u65E0\u6548", localOffset, label);
|
|
184
|
+
}
|
|
185
|
+
if (entry.method === METHOD_DEFLATE && entry.uncompressedSize > maxEntrySizeBytes) {
|
|
186
|
+
fail(
|
|
187
|
+
`\u6761\u76EE "${entry.name}" \u58F0\u660E\u7684\u89E3\u538B\u5C3A\u5BF8 ${String(entry.uncompressedSize)} \u5B57\u8282\u8D85\u8FC7\u4E0A\u9650 ${String(maxEntrySizeBytes)} \u5B57\u8282`,
|
|
188
|
+
localOffset,
|
|
189
|
+
label
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const nameLength = view.getUint16(localOffset + 26, true);
|
|
193
|
+
const extraLength = view.getUint16(localOffset + 28, true);
|
|
194
|
+
const dataStart = localOffset + LOCAL_HEADER_SIZE + nameLength + extraLength;
|
|
195
|
+
const dataEnd = dataStart + entry.compressedSize;
|
|
196
|
+
if (dataEnd > bytes.length) fail("\u6761\u76EE\u6570\u636E\u8D8A\u754C", dataStart, label);
|
|
197
|
+
const compressed = bytes.subarray(dataStart, dataEnd);
|
|
198
|
+
const raw = entry.method === METHOD_STORED ? compressed : await inflateRaw(compressed, label, maxEntrySizeBytes);
|
|
199
|
+
if (raw.length !== entry.uncompressedSize) {
|
|
200
|
+
fail(
|
|
201
|
+
`\u6761\u76EE "${entry.name}" \u957F\u5EA6\u4E0D\u7B26\uFF1A\u671F\u671B ${String(entry.uncompressedSize)}\uFF0C\u5B9E\u9645 ${String(raw.length)}`,
|
|
202
|
+
dataStart,
|
|
203
|
+
label
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const actualCrc = crc32(raw);
|
|
207
|
+
if (actualCrc !== entry.crc32) {
|
|
208
|
+
fail(
|
|
209
|
+
`\u6761\u76EE "${entry.name}" CRC-32 \u6821\u9A8C\u5931\u8D25\uFF1A\u671F\u671B ${String(entry.crc32)}\uFF0C\u5B9E\u9645 ${String(actualCrc)}`,
|
|
210
|
+
dataStart,
|
|
211
|
+
label
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return entry.method === METHOD_STORED ? new Uint8Array(raw) : raw;
|
|
215
|
+
}
|
|
216
|
+
function createArchive(bytes, records, label, maxEntrySizeBytes) {
|
|
217
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const record of records) byKey.set(chunkIIV6VIUJ_cjs.normalizeArchivePath(record.entry.name), record);
|
|
219
|
+
const find = (name) => {
|
|
220
|
+
const key = lookupKey(name);
|
|
221
|
+
return key === void 0 ? void 0 : byKey.get(key);
|
|
222
|
+
};
|
|
223
|
+
const archive = {
|
|
224
|
+
entries: records.map((record) => record.entry),
|
|
225
|
+
entry(name) {
|
|
226
|
+
return find(name)?.entry;
|
|
227
|
+
},
|
|
228
|
+
async read(name, options) {
|
|
229
|
+
chunkG7DLWGBW_cjs.throwIfAborted(options?.signal);
|
|
230
|
+
const record = find(name);
|
|
231
|
+
if (record === void 0) formatFail(`\u6761\u76EE\u4E0D\u5B58\u5728 "${name}"`, label);
|
|
232
|
+
if (record.entry.isDirectory) formatFail(`\u6761\u76EE "${name}" \u662F\u76EE\u5F55\uFF0C\u65E0\u5185\u5BB9`, label);
|
|
233
|
+
const data = await readEntry(bytes, record, label, maxEntrySizeBytes);
|
|
234
|
+
return data;
|
|
235
|
+
},
|
|
236
|
+
async readText(name, options) {
|
|
237
|
+
const data = await archive.read(name, options);
|
|
238
|
+
return textDecoder.decode(data);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
return archive;
|
|
242
|
+
}
|
|
243
|
+
function parseZip(bytes, options) {
|
|
244
|
+
const label = options?.source ?? "<anonymous>";
|
|
245
|
+
const maxEntrySizeBytes = options?.maxEntrySizeBytes ?? DEFAULT_MAX_ENTRY_SIZE_BYTES;
|
|
246
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
247
|
+
const eocd = findEocd(view, bytes.length, label);
|
|
248
|
+
const records = readCentralDirectory(bytes, view, eocd, label);
|
|
249
|
+
return createArchive(bytes, records, label, maxEntrySizeBytes);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
exports.parseZip = parseZip;
|