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