@storyteller-platform/epub 0.8.2 → 1.0.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 +4236 -1017
- package/dist/adapters/interface.d.ts +20 -1
- package/dist/adapters/memory.d.ts +2 -2
- package/dist/adapters/memory.js +16 -30
- package/dist/adapters/tmpfs.browser.d.ts +11 -0
- package/dist/adapters/tmpfs.browser.js +9 -0
- package/dist/adapters/tmpfs.d.ts +2 -2
- package/dist/adapters/tmpfs.js +22 -35
- package/dist/files.browser.d.ts +2 -0
- package/dist/files.browser.js +7 -0
- package/dist/files.node.d.ts +2 -0
- package/dist/files.node.js +10 -0
- package/dist/index.d.ts +44 -122
- package/dist/index.js +380 -654
- package/dist/jsx-dev-runtime.d.ts +9 -0
- package/dist/jsx-dev-runtime.js +9 -0
- package/dist/jsx-runtime.d.ts +42 -0
- package/dist/jsx-runtime.js +69 -0
- package/dist/upgrade.d.ts +26 -17
- package/dist/upgrade.js +118 -159
- package/dist/xml.d.ts +104 -0
- package/dist/xml.js +251 -0
- package/dist/zip.browser.d.ts +2 -0
- package/dist/zip.browser.js +33 -0
- package/dist/zip.node.d.ts +9 -0
- package/dist/zip.node.js +246 -0
- package/package.json +92 -10
- package/dist/epubcheck.d.ts +0 -21
- package/dist/epubcheck.js +0 -34
- package/dist/zip.d.ts +0 -2
- package/dist/zip.js +0 -137
package/dist/upgrade.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@storyteller-platform/epub/jsx-runtime";
|
|
2
2
|
import { MediaType } from "@storyteller-platform/media-types";
|
|
3
|
-
import {
|
|
3
|
+
import { findXmlChildByNameNS, findXmlDescendantByName, findXmlDescendantByNameNS, insert, NS, replace, serialize, text, x, xmlId, } from "./xml.js";
|
|
4
4
|
const GUIDE_TO_EPUBTYPE = {
|
|
5
5
|
acknowledgements: "acknowledgments",
|
|
6
6
|
"other.afterword": "afterword",
|
|
@@ -50,54 +50,52 @@ const GUIDE_TO_EPUBTYPE = {
|
|
|
50
50
|
const XHTML_MEDIA_TYPES = new Set([MediaType.XHTML, MediaType.HTML]);
|
|
51
51
|
// xml helpers
|
|
52
52
|
function getMetadataElement(pkg) {
|
|
53
|
-
return
|
|
53
|
+
return findXmlChildByNameNS(NS.opf, "metadata", pkg);
|
|
54
54
|
}
|
|
55
55
|
function getManifestElement(pkg) {
|
|
56
|
-
return
|
|
56
|
+
return findXmlChildByNameNS(NS.opf, "manifest", pkg);
|
|
57
57
|
}
|
|
58
58
|
function getSpineElement(pkg) {
|
|
59
|
-
return
|
|
59
|
+
return findXmlChildByNameNS(NS.opf, "spine", pkg);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Replace an outdated document type declaration with `<!DOCTYPE html>`.
|
|
63
|
+
*
|
|
64
|
+
* EPUB 3 XHTML content documents must conform to the HTML5 conformance
|
|
65
|
+
* criteria, which require `<!DOCTYPE html>`.
|
|
66
|
+
*/
|
|
67
|
+
export function normalizeDoctype(doc) {
|
|
68
|
+
const doctype = doc.doctype;
|
|
69
|
+
if (doctype && doctype.publicId === "" && doctype.systemId === "")
|
|
70
|
+
return;
|
|
71
|
+
if (doctype)
|
|
72
|
+
doc.removeChild(doctype);
|
|
73
|
+
doc.insertBefore(doc.implementation.createDocumentType("html", "", ""), doc.documentElement);
|
|
74
|
+
return;
|
|
60
75
|
}
|
|
61
76
|
function ensureId(element) {
|
|
62
|
-
const existing = element
|
|
77
|
+
const existing = element.getAttribute("id");
|
|
63
78
|
if (existing)
|
|
64
79
|
return existing;
|
|
65
|
-
const id = `id-${
|
|
66
|
-
element
|
|
67
|
-
element[":@"]["@_id"] = id;
|
|
80
|
+
const id = `id-${xmlId(8)}`;
|
|
81
|
+
element.setAttribute("id", id);
|
|
68
82
|
return id;
|
|
69
83
|
}
|
|
70
|
-
function
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
function hasElementDeep(xml, name) {
|
|
74
|
-
for (const node of xml) {
|
|
75
|
-
if (Epub.isXmlTextNode(node))
|
|
76
|
-
continue;
|
|
77
|
-
if (Epub.getXmlElementName(node) === name)
|
|
78
|
-
return true;
|
|
79
|
-
if (hasElementDeep(Epub.getXmlChildren(node), name))
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
function textContentOf(element) {
|
|
85
|
-
return Epub.getXhtmlTextContent(Epub.getXmlChildren(element)).trim();
|
|
84
|
+
function findAllByNameNS(namespaceUri, name, element) {
|
|
85
|
+
return element.children.filter((node) => (!namespaceUri || node.namespaceURI === namespaceUri) &&
|
|
86
|
+
node.localName === name);
|
|
86
87
|
}
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
if (idx !== -1)
|
|
90
|
-
array.splice(idx, 1);
|
|
88
|
+
function findAllByName(name, element) {
|
|
89
|
+
return findAllByNameNS(null, name, element);
|
|
91
90
|
}
|
|
92
91
|
// ---------------------------------------------------------------------------
|
|
93
92
|
export function upgradeIdentifiers(pkg) {
|
|
94
93
|
const metadata = getMetadataElement(pkg);
|
|
95
94
|
if (!metadata)
|
|
96
95
|
return;
|
|
97
|
-
for (const ident of
|
|
98
|
-
|
|
99
|
-
let
|
|
100
|
-
let scheme = attrs["@_opf:scheme"];
|
|
96
|
+
for (const ident of findAllByNameNS(NS.dc, "identifier", metadata)) {
|
|
97
|
+
let val = ident.textContent ?? "";
|
|
98
|
+
let scheme = ident.getAttributeNS(NS.opf, "scheme");
|
|
101
99
|
if (val.toLowerCase().startsWith("urn:")) {
|
|
102
100
|
const rest = val.slice(4);
|
|
103
101
|
const colonIdx = rest.indexOf(":");
|
|
@@ -109,21 +107,20 @@ export function upgradeIdentifiers(pkg) {
|
|
|
109
107
|
if (scheme && val && !scheme.toLowerCase().startsWith("uri")) {
|
|
110
108
|
val = `${scheme}:${val}`;
|
|
111
109
|
}
|
|
112
|
-
const id =
|
|
113
|
-
|
|
114
|
-
Epub.replaceXmlChildren(ident, [Epub.createXmlTextNode(val)]);
|
|
110
|
+
const id = ident.getAttribute("id");
|
|
111
|
+
replace(metadata, _jsx("dc:identifier", { id: id, children: val }), ident);
|
|
115
112
|
}
|
|
116
113
|
}
|
|
117
114
|
export function upgradeTitle(pkg) {
|
|
118
115
|
const metadata = getMetadataElement(pkg);
|
|
119
116
|
if (!metadata)
|
|
120
117
|
return;
|
|
121
|
-
const titles =
|
|
118
|
+
const titles = findAllByNameNS(NS.dc, "title", metadata);
|
|
122
119
|
let firstTitle = null;
|
|
123
120
|
for (const title of titles) {
|
|
124
|
-
const text =
|
|
121
|
+
const text = title.textContent?.trim();
|
|
125
122
|
if (!text) {
|
|
126
|
-
|
|
123
|
+
metadata.removeChild(title);
|
|
127
124
|
continue;
|
|
128
125
|
}
|
|
129
126
|
firstTitle ??= title;
|
|
@@ -131,43 +128,38 @@ export function upgradeTitle(pkg) {
|
|
|
131
128
|
if (!firstTitle)
|
|
132
129
|
return;
|
|
133
130
|
const titleId = ensureId(firstTitle);
|
|
134
|
-
metadata
|
|
131
|
+
insert(metadata, _jsx("meta", { refines: `#${titleId}`, property: "title-type", children: "main" }));
|
|
135
132
|
}
|
|
136
133
|
export function upgradeLanguages(pkg) {
|
|
137
134
|
const metadata = getMetadataElement(pkg);
|
|
138
135
|
if (!metadata)
|
|
139
136
|
return;
|
|
140
|
-
const langs =
|
|
137
|
+
const langs = findAllByNameNS(NS.dc, "language", metadata);
|
|
141
138
|
if (langs.length > 0) {
|
|
142
139
|
for (const lang of langs) {
|
|
143
|
-
const id = lang
|
|
144
|
-
|
|
140
|
+
const id = lang.getAttribute("id");
|
|
141
|
+
replace(metadata, _jsx("dc:language", { id: id, children: Array.from(lang.childNodes) }), lang);
|
|
145
142
|
}
|
|
146
143
|
return;
|
|
147
144
|
}
|
|
148
|
-
metadata
|
|
145
|
+
insert(metadata, _jsx("dc:language", { children: "und" }));
|
|
149
146
|
}
|
|
150
147
|
export function upgradeAuthors(pkg) {
|
|
151
148
|
const metadata = getMetadataElement(pkg);
|
|
152
149
|
if (!metadata)
|
|
153
150
|
return;
|
|
154
|
-
for (const which of ["
|
|
155
|
-
for (const elem of
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const elemId = role || sort ? ensureId(elem) : attrs["@_id"];
|
|
151
|
+
for (const which of ["creator", "contributor"]) {
|
|
152
|
+
for (const elem of findAllByNameNS(NS.dc, which, metadata)) {
|
|
153
|
+
const role = elem.getAttributeNS(NS.opf, "role");
|
|
154
|
+
const sort = elem.getAttributeNS(NS.opf, "file-as");
|
|
155
|
+
const elemId = role || sort ? ensureId(elem) : elem.getAttribute("id");
|
|
160
156
|
// strip all non-id attributes
|
|
161
|
-
|
|
157
|
+
replace(metadata, x(`dc:${which}`, elemId === null ? null : { id: elemId }, ...elem.childNodes), elem);
|
|
162
158
|
if (role) {
|
|
163
|
-
metadata
|
|
164
|
-
refines: `#${elemId}`,
|
|
165
|
-
property: "role",
|
|
166
|
-
scheme: "marc:relators",
|
|
167
|
-
}, [Epub.createXmlTextNode(role)]));
|
|
159
|
+
insert(metadata, _jsx("meta", { refines: `#${elemId}`, property: "role", scheme: "marc:relators", children: role }));
|
|
168
160
|
}
|
|
169
161
|
if (sort) {
|
|
170
|
-
metadata
|
|
162
|
+
insert(metadata, _jsx("meta", { refines: `#${elemId}`, property: "file-as", children: sort }));
|
|
171
163
|
}
|
|
172
164
|
}
|
|
173
165
|
}
|
|
@@ -176,27 +168,26 @@ export function upgradeDate(pkg) {
|
|
|
176
168
|
const metadata = getMetadataElement(pkg);
|
|
177
169
|
if (!metadata)
|
|
178
170
|
return;
|
|
179
|
-
const dates =
|
|
171
|
+
const dates = findAllByNameNS(NS.dc, "date", metadata);
|
|
180
172
|
let kept = false;
|
|
181
173
|
for (const date of dates) {
|
|
182
|
-
const text =
|
|
174
|
+
const text = date.textContent;
|
|
183
175
|
if (!text || kept) {
|
|
184
|
-
|
|
176
|
+
metadata.removeChild(date);
|
|
185
177
|
continue;
|
|
186
178
|
}
|
|
187
179
|
kept = true;
|
|
188
|
-
const id = date
|
|
189
|
-
date
|
|
180
|
+
const id = date.getAttribute("id");
|
|
181
|
+
replace(metadata, _jsx("dc:date", { id: id, children: Array.from(date.childNodes) }), date);
|
|
190
182
|
}
|
|
191
183
|
}
|
|
192
184
|
export function upgradeMeta(pkg) {
|
|
193
185
|
const metadata = getMetadataElement(pkg);
|
|
194
186
|
if (!metadata)
|
|
195
187
|
return;
|
|
196
|
-
for (const meta of findAllByName("meta", metadata
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const content = attrs["@_content"] ?? "";
|
|
188
|
+
for (const meta of findAllByName("meta", metadata)) {
|
|
189
|
+
const name = meta.getAttribute("name") ?? "";
|
|
190
|
+
const content = meta.getAttribute("content") ?? "";
|
|
200
191
|
let prop = null;
|
|
201
192
|
let value = content;
|
|
202
193
|
const cleanName = name.startsWith("rendition:")
|
|
@@ -219,10 +210,13 @@ export function upgradeMeta(pkg) {
|
|
|
219
210
|
}
|
|
220
211
|
if (!prop)
|
|
221
212
|
continue;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
213
|
+
meta.removeAttribute("name");
|
|
214
|
+
meta.removeAttribute("content");
|
|
215
|
+
meta.setAttribute("property", prop);
|
|
216
|
+
while (meta.firstChild) {
|
|
217
|
+
meta.removeChild(meta.firstChild);
|
|
218
|
+
}
|
|
219
|
+
insert(meta, text(value));
|
|
226
220
|
}
|
|
227
221
|
}
|
|
228
222
|
export function upgradeCover(pkg) {
|
|
@@ -230,27 +224,26 @@ export function upgradeCover(pkg) {
|
|
|
230
224
|
const manifest = getManifestElement(pkg);
|
|
231
225
|
if (!metadata || !manifest)
|
|
232
226
|
return;
|
|
233
|
-
for (const meta of findAllByName("meta", metadata
|
|
234
|
-
const
|
|
235
|
-
const isCoverMeta =
|
|
227
|
+
for (const meta of findAllByName("meta", metadata)) {
|
|
228
|
+
const content = meta.getAttribute("content");
|
|
229
|
+
const isCoverMeta = meta.getAttribute("name") === "cover" && content;
|
|
236
230
|
if (!isCoverMeta)
|
|
237
231
|
continue;
|
|
238
|
-
const itemId =
|
|
239
|
-
for (const item of findAllByName("item", manifest
|
|
240
|
-
|
|
241
|
-
if (!itemAttrs || itemAttrs["@_id"] !== itemId)
|
|
232
|
+
const itemId = content;
|
|
233
|
+
for (const item of findAllByName("item", manifest)) {
|
|
234
|
+
if (item.getAttribute("id") !== itemId)
|
|
242
235
|
continue;
|
|
243
|
-
const mediaType = (
|
|
236
|
+
const mediaType = (item.getAttribute("media-type") ?? "").toLowerCase();
|
|
244
237
|
// only mark actual images, not html wrappers
|
|
245
238
|
const isImage = mediaType && !mediaType.includes("xml") && !mediaType.includes("html");
|
|
246
239
|
if (!isImage)
|
|
247
240
|
continue;
|
|
248
|
-
const existing = (
|
|
241
|
+
const existing = (item.getAttribute("properties") ?? "")
|
|
249
242
|
.split(" ")
|
|
250
243
|
.filter(Boolean);
|
|
251
244
|
const props = new Set(existing);
|
|
252
245
|
props.add("cover-image");
|
|
253
|
-
|
|
246
|
+
item.setAttribute("properties", [...props].sort().join(" "));
|
|
254
247
|
}
|
|
255
248
|
}
|
|
256
249
|
}
|
|
@@ -258,35 +251,27 @@ export function removeInvalidDcAttrs(pkg) {
|
|
|
258
251
|
const metadata = getMetadataElement(pkg);
|
|
259
252
|
if (!metadata)
|
|
260
253
|
return;
|
|
261
|
-
for (const node of metadata.
|
|
262
|
-
if (
|
|
263
|
-
continue;
|
|
264
|
-
const name = Epub.getXmlElementName(node);
|
|
265
|
-
if (!name.startsWith("dc:"))
|
|
254
|
+
for (const node of Array.from(metadata.children)) {
|
|
255
|
+
if (node.namespaceURI !== NS.dc)
|
|
266
256
|
continue;
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
257
|
+
for (const attr of Array.from(node.attributes)) {
|
|
258
|
+
if (attr.name === "id" || attr.name.startsWith("xmlns:"))
|
|
259
|
+
continue;
|
|
260
|
+
node.removeAttributeNode(attr);
|
|
261
|
+
}
|
|
272
262
|
}
|
|
273
263
|
}
|
|
274
264
|
export function setLastModified(pkg) {
|
|
275
265
|
const metadata = getMetadataElement(pkg);
|
|
276
266
|
if (!metadata)
|
|
277
267
|
return;
|
|
278
|
-
for (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
continue;
|
|
282
|
-
if (node[":@"]?.["@_property"] === "dcterms:modified") {
|
|
283
|
-
metadata.metadata.splice(i, 1);
|
|
268
|
+
for (const node of Array.from(metadata.children)) {
|
|
269
|
+
if (node.getAttribute("property") === "dcterms:modified") {
|
|
270
|
+
metadata.removeChild(node);
|
|
284
271
|
}
|
|
285
272
|
}
|
|
286
273
|
const now = new Date().toISOString().replace(/\.\d+/, "");
|
|
287
|
-
metadata
|
|
288
|
-
Epub.createXmlTextNode(now),
|
|
289
|
-
]));
|
|
274
|
+
insert(metadata, _jsx("meta", { property: "dcterms:modified", children: now }));
|
|
290
275
|
}
|
|
291
276
|
export function upgradePackageMetadata(pkg) {
|
|
292
277
|
upgradeIdentifiers(pkg);
|
|
@@ -300,19 +285,16 @@ export function upgradePackageMetadata(pkg) {
|
|
|
300
285
|
setLastModified(pkg);
|
|
301
286
|
}
|
|
302
287
|
export function extractGuideLandmarks(pkg) {
|
|
303
|
-
const guide =
|
|
288
|
+
const guide = findXmlChildByNameNS(NS.opf, "guide", pkg);
|
|
304
289
|
if (!guide)
|
|
305
290
|
return [];
|
|
306
291
|
const landmarks = [];
|
|
307
|
-
for (const node of
|
|
308
|
-
if (Epub.isXmlTextNode(node))
|
|
309
|
-
continue;
|
|
292
|
+
for (const node of Array.from(guide.children)) {
|
|
310
293
|
if (!("reference" in node))
|
|
311
294
|
continue;
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
const
|
|
315
|
-
const guideType = (attrs["@_type"] ?? "").toLowerCase();
|
|
295
|
+
const href = node.getAttribute("href") ?? "";
|
|
296
|
+
const title = node.getAttribute("title") ?? "";
|
|
297
|
+
const guideType = (node.getAttribute("type") ?? "").toLowerCase();
|
|
316
298
|
const epubType = GUIDE_TO_EPUBTYPE[guideType];
|
|
317
299
|
if (epubType === undefined || !href)
|
|
318
300
|
continue;
|
|
@@ -321,17 +303,14 @@ export function extractGuideLandmarks(pkg) {
|
|
|
321
303
|
return landmarks;
|
|
322
304
|
}
|
|
323
305
|
export function removeGuide(pkg) {
|
|
324
|
-
const
|
|
325
|
-
const guide = Epub.findXmlChildByName("guide", children);
|
|
306
|
+
const guide = findXmlChildByNameNS(NS.opf, "guide", pkg);
|
|
326
307
|
if (guide) {
|
|
327
|
-
|
|
308
|
+
pkg.removeChild(guide);
|
|
328
309
|
}
|
|
329
310
|
}
|
|
330
311
|
export function removeSpineTocRef(pkg) {
|
|
331
312
|
const spine = getSpineElement(pkg);
|
|
332
|
-
|
|
333
|
-
return;
|
|
334
|
-
delete spine[":@"]["@_toc"];
|
|
313
|
+
spine?.removeAttribute("toc");
|
|
335
314
|
}
|
|
336
315
|
export async function collectManifestProperties(epub) {
|
|
337
316
|
const manifest = await epub.getManifest();
|
|
@@ -339,22 +318,25 @@ export async function collectManifestProperties(epub) {
|
|
|
339
318
|
const mediaType = MediaType.fromMime(item.mediaType ?? "");
|
|
340
319
|
if (!mediaType || !XHTML_MEDIA_TYPES.has(mediaType))
|
|
341
320
|
continue;
|
|
342
|
-
let
|
|
321
|
+
let documentElement;
|
|
343
322
|
try {
|
|
344
|
-
|
|
323
|
+
documentElement = (await epub.readXhtmlItemContents(item.id))
|
|
324
|
+
.documentElement;
|
|
345
325
|
}
|
|
346
326
|
catch {
|
|
347
327
|
continue;
|
|
348
328
|
}
|
|
329
|
+
if (!documentElement)
|
|
330
|
+
continue;
|
|
349
331
|
const props = new Set(item.properties ?? []);
|
|
350
332
|
const before = props.size;
|
|
351
|
-
if (
|
|
333
|
+
if (findXmlDescendantByName("svg", documentElement))
|
|
352
334
|
props.add("svg");
|
|
353
|
-
if (
|
|
335
|
+
if (findXmlDescendantByName("script", documentElement))
|
|
354
336
|
props.add("scripted");
|
|
355
|
-
if (
|
|
337
|
+
if (findXmlDescendantByName("math", documentElement))
|
|
356
338
|
props.add("mathml");
|
|
357
|
-
if (
|
|
339
|
+
if (findXmlDescendantByNameNS(NS.epub, "switch", documentElement))
|
|
358
340
|
props.add("switch");
|
|
359
341
|
if (props.size === before)
|
|
360
342
|
continue;
|
|
@@ -368,15 +350,14 @@ export function fixFontMimeTypes(pkg) {
|
|
|
368
350
|
const manifest = getManifestElement(pkg);
|
|
369
351
|
if (!manifest)
|
|
370
352
|
return;
|
|
371
|
-
for (const item of findAllByName("item", manifest
|
|
372
|
-
const declared = item
|
|
353
|
+
for (const item of findAllByName("item", manifest)) {
|
|
354
|
+
const declared = item.getAttribute("media-type") ?? "";
|
|
373
355
|
if (!MediaType.fromMime(declared)?.is("font"))
|
|
374
356
|
continue;
|
|
375
|
-
const corrected = MediaType.fromPath(item
|
|
357
|
+
const corrected = MediaType.fromPath(item.getAttribute("href") ?? "");
|
|
376
358
|
if (!corrected?.is("font") || corrected.mime === declared)
|
|
377
359
|
continue;
|
|
378
|
-
item
|
|
379
|
-
item[":@"]["@_media-type"] = corrected.mime;
|
|
360
|
+
item.setAttribute("media-type", corrected.mime);
|
|
380
361
|
}
|
|
381
362
|
}
|
|
382
363
|
export function buildTocOl(entries) {
|
|
@@ -384,19 +365,17 @@ export function buildTocOl(entries) {
|
|
|
384
365
|
const label = entry.title.replace(/\s+/g, " ").trim();
|
|
385
366
|
const liChildren = [];
|
|
386
367
|
if (entry.href) {
|
|
387
|
-
liChildren.push(
|
|
388
|
-
Epub.createXmlTextNode(label),
|
|
389
|
-
]));
|
|
368
|
+
liChildren.push(_jsx("a", { href: entry.href, children: label }));
|
|
390
369
|
}
|
|
391
370
|
else {
|
|
392
|
-
liChildren.push(
|
|
371
|
+
liChildren.push(_jsx("span", { children: label }));
|
|
393
372
|
}
|
|
394
373
|
if (entry.children && entry.children.length > 0) {
|
|
395
374
|
liChildren.push(buildTocOl(entry.children));
|
|
396
375
|
}
|
|
397
|
-
return
|
|
376
|
+
return _jsx("li", { children: liChildren });
|
|
398
377
|
});
|
|
399
|
-
return
|
|
378
|
+
return _jsx("ol", { children: children });
|
|
400
379
|
}
|
|
401
380
|
export async function buildNavDocument(epub, tocEntries, landmarks) {
|
|
402
381
|
let tocOl;
|
|
@@ -407,41 +386,21 @@ export async function buildNavDocument(epub, tocEntries, landmarks) {
|
|
|
407
386
|
// epub 3 requires a non-empty <ol>, fall back to first spine item
|
|
408
387
|
const spineItems = await epub.getSpineItems();
|
|
409
388
|
const firstHref = spineItems[0]?.href ?? "#";
|
|
410
|
-
tocOl =
|
|
411
|
-
Epub.createXmlElement("li", {}, [
|
|
412
|
-
Epub.createXmlElement("a", { href: firstHref }, [
|
|
413
|
-
Epub.createXmlTextNode("Start"),
|
|
414
|
-
]),
|
|
415
|
-
]),
|
|
416
|
-
]);
|
|
389
|
+
tocOl = (_jsx("ol", { children: _jsx("li", { children: _jsx("a", { href: firstHref, children: "Start" }) }) }));
|
|
417
390
|
}
|
|
418
|
-
const tocNav =
|
|
419
|
-
|
|
420
|
-
Epub.createXmlTextNode("Table of Contents"),
|
|
421
|
-
]),
|
|
422
|
-
tocOl,
|
|
423
|
-
]);
|
|
424
|
-
const body = [tocNav];
|
|
391
|
+
const tocNav = (_jsxs("nav", { "epub:type": "toc", children: [_jsx("h1", { children: "Table of Contents" }), tocOl] }));
|
|
392
|
+
const bodyChildren = [tocNav];
|
|
425
393
|
const validLandmarks = landmarks.filter((lm) => lm.type);
|
|
426
394
|
if (validLandmarks.length > 0) {
|
|
427
|
-
const lmOl =
|
|
428
|
-
|
|
429
|
-
Epub.createXmlTextNode(lm.title || lm.type),
|
|
430
|
-
]),
|
|
431
|
-
])));
|
|
432
|
-
body.push(Epub.createXmlElement("nav", { "epub:type": "landmarks", hidden: "" }, [
|
|
433
|
-
lmOl,
|
|
434
|
-
]));
|
|
395
|
+
const lmOl = (_jsx("ol", { children: validLandmarks.map((lm) => (_jsx("li", { children: _jsx("a", { "epub:type": lm.type, href: lm.href, children: lm.title || lm.type }) }))) }));
|
|
396
|
+
bodyChildren.push(_jsx("nav", { "epub:type": "landmarks", hidden: "", children: lmOl }));
|
|
435
397
|
}
|
|
436
|
-
const head = [
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const navDoc = await epub.createXhtmlDocument(body, head);
|
|
440
|
-
return Epub.xhtmlBuilder.build(navDoc);
|
|
398
|
+
const head = [_jsx("title", { children: "Navigation" })];
|
|
399
|
+
const navDoc = await epub.createXhtmlDocument(bodyChildren, head);
|
|
400
|
+
return serialize(navDoc);
|
|
441
401
|
}
|
|
442
402
|
export function setPackageVersion(pkg, version) {
|
|
443
|
-
pkg
|
|
444
|
-
pkg[":@"]["@_version"] = version;
|
|
403
|
+
pkg.setAttribute("version", version);
|
|
445
404
|
}
|
|
446
405
|
export async function chooseNavHref(epub) {
|
|
447
406
|
const manifest = await epub.getManifest();
|
package/dist/xml.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import xmldom, { type Document, type CharacterData, type Element, type Node, type Text, type DocumentType } from "@xmldom/xmldom";
|
|
2
|
+
export declare const xmlId: (size?: number) => string;
|
|
3
|
+
export declare const XMLSerializer: typeof xmldom.XMLSerializer;
|
|
4
|
+
export declare function isElement(n: Node): n is Element;
|
|
5
|
+
export declare function isText(n: Node): n is CharacterData;
|
|
6
|
+
export declare function isCdata(n: Node): n is CharacterData;
|
|
7
|
+
export declare function isComment(n: Node): n is CharacterData;
|
|
8
|
+
/**
|
|
9
|
+
* Give every element in `node`'s subtree the namespace its position implies,
|
|
10
|
+
* without overwriting any element that already has one.
|
|
11
|
+
*
|
|
12
|
+
* @privateremarks
|
|
13
|
+
*
|
|
14
|
+
* `x()` builds elements in no namespace. That serializes correctly, because a
|
|
15
|
+
* child in no namespace inherits the enclosing default namespace on the way
|
|
16
|
+
* out, but in memory the elements report `namespaceURI === null` and so are
|
|
17
|
+
* invisible to namespace-aware queries such as `getElementsByTagNameNS`.
|
|
18
|
+
* Calling this after inserting a node makes the tree report the namespace its
|
|
19
|
+
* position implies.
|
|
20
|
+
*/
|
|
21
|
+
export declare function adoptNamespaces(node: Element): void;
|
|
22
|
+
/**
|
|
23
|
+
* Declare `uri` as the default namespace of `element` and bring the elements
|
|
24
|
+
* already under it in line with that declaration.
|
|
25
|
+
*
|
|
26
|
+
* xmldom records a namespace declaration without applying it to the tree, so a
|
|
27
|
+
* document read without `xmlns` stays in no namespace in memory even once the
|
|
28
|
+
* declaration is added. Namespace-aware lookups cannot see any of it until the
|
|
29
|
+
* subtree is fixed up, and the document element has no element parent for
|
|
30
|
+
* `adoptNamespaces` to inherit from, so it has to be bound explicitly.
|
|
31
|
+
*/
|
|
32
|
+
export declare function declareDefaultNamespace(element: Element, uri: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Insert `child` into `parent`, then adopt its namespace.
|
|
35
|
+
*
|
|
36
|
+
* If `reference` is omitted, appends child to parent.
|
|
37
|
+
*/
|
|
38
|
+
export declare function insert<T extends Node>(parent: Element, child: T, reference?: Node | null): T;
|
|
39
|
+
/**
|
|
40
|
+
* Replace `oldChild` with `newChild` in `parent`, then adopt the new child's
|
|
41
|
+
* namespace.
|
|
42
|
+
*/
|
|
43
|
+
export declare function replace<T extends Node>(parent: Element, newChild: T, oldChild: Node): T;
|
|
44
|
+
/**
|
|
45
|
+
* Given an XHTML document, return the body element.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getXhtmlBody(document: Document): Element;
|
|
48
|
+
/**
|
|
49
|
+
* Given an Element, find the first child matching
|
|
50
|
+
* the provided namespace, name and optional filter.
|
|
51
|
+
*/
|
|
52
|
+
export declare function findXmlChildByNameNS(namespaceUri: string | null, name: string, element: Element, filter?: (node: Element) => boolean): Element | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Given an Element, find the first child matching
|
|
55
|
+
* the provided name and optional filter.
|
|
56
|
+
*/
|
|
57
|
+
export declare function findXmlChildByName(name: string, element: Element, filter?: (node: Element) => boolean): Element | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Given an Element, find the first descendant matching
|
|
60
|
+
* the provided name and optional filter.
|
|
61
|
+
*
|
|
62
|
+
* Will perform a breadth first search for the element, returning
|
|
63
|
+
* the highest element in the tree matching the name and filter.
|
|
64
|
+
*/
|
|
65
|
+
export declare function findXmlDescendantByNameNS(namespaceUri: string | null, name: string, element: Element, filter?: (node: Element) => boolean): Element | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Given an Element, find the first descendant matching
|
|
68
|
+
* the provided name and optional filter.
|
|
69
|
+
*
|
|
70
|
+
* Will perform a breadth first search for the element, returning
|
|
71
|
+
* the highest element in the tree matching the name and filter.
|
|
72
|
+
*/
|
|
73
|
+
export declare function findXmlDescendantByName(name: string, element: Element, filter?: (node: Element) => boolean): Element | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Given an XHTML document, add a `link` element to the
|
|
76
|
+
* `head` of the document.
|
|
77
|
+
*/
|
|
78
|
+
export declare function addLinkToXhtmlHead(document: Document, link: {
|
|
79
|
+
rel: string;
|
|
80
|
+
href: string;
|
|
81
|
+
type: string;
|
|
82
|
+
}): void;
|
|
83
|
+
export type XmlDiagnostic = {
|
|
84
|
+
level: "warning" | "error" | "fatalError";
|
|
85
|
+
message: string;
|
|
86
|
+
line?: number | undefined;
|
|
87
|
+
column?: number | undefined;
|
|
88
|
+
};
|
|
89
|
+
export declare function parse(xml: string, mediaType: string, onError?: (diagnostic: XmlDiagnostic) => void): Document;
|
|
90
|
+
export declare function serialize(doc: Node, options?: {
|
|
91
|
+
validate?: boolean | undefined;
|
|
92
|
+
}): string;
|
|
93
|
+
export declare const NS: {
|
|
94
|
+
xmlns: string;
|
|
95
|
+
xml: string;
|
|
96
|
+
xhtml: string;
|
|
97
|
+
opf: string;
|
|
98
|
+
dc: string;
|
|
99
|
+
epub: string;
|
|
100
|
+
};
|
|
101
|
+
export declare function x(name: string, attributes?: Record<string, string | undefined> | null, ...children: Array<Node | string | null | undefined>): Element;
|
|
102
|
+
export declare function text(data: string): Text;
|
|
103
|
+
export declare function createDocument(namespaceUri: string | null, qualifiedName: string, docType?: DocumentType | null): Document;
|
|
104
|
+
export declare function createDocumentType(qualifiedName: string, publicId?: string, systemId?: string): DocumentType;
|