@storyteller-platform/epub 0.7.0 → 0.8.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 +762 -1054
- package/dist/adapters/interface.d.ts +31 -27
- package/dist/adapters/interface.js +1 -0
- package/dist/adapters/memory.d.ts +7 -11
- package/dist/adapters/memory.js +70 -78
- package/dist/adapters/tmpfs.d.ts +11 -12
- package/dist/adapters/tmpfs.js +238 -165
- package/dist/epubcheck.d.ts +4 -6
- package/dist/epubcheck.js +25 -27
- package/dist/index.d.ts +1161 -5
- package/dist/index.js +2595 -2661
- package/dist/upgrade.d.ts +23 -1169
- package/dist/upgrade.js +411 -463
- package/dist/zip.d.ts +2 -0
- package/dist/zip.js +137 -0
- package/package.json +18 -32
- package/dist/adapters/interface.cjs +0 -16
- package/dist/adapters/interface.d.cts +0 -69
- package/dist/adapters/memory.cjs +0 -103
- package/dist/adapters/memory.d.cts +0 -37
- package/dist/adapters/tmpfs.cjs +0 -236
- package/dist/adapters/tmpfs.d.cts +0 -29
- package/dist/chunk-BIEQXUOY.js +0 -50
- package/dist/epubcheck.cjs +0 -60
- package/dist/epubcheck.d.cts +0 -23
- package/dist/index.cjs +0 -2749
- package/dist/index.d.cts +0 -5
- package/dist/upgrade.cjs +0 -555
- package/dist/upgrade.d.cts +0 -1182
package/dist/upgrade.js
CHANGED
|
@@ -1,515 +1,463 @@
|
|
|
1
|
-
import "./chunk-BIEQXUOY.js";
|
|
2
1
|
import { nanoid } from "nanoid";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
} from "./index.js";
|
|
2
|
+
import { MediaType } from "@storyteller-platform/media-types";
|
|
3
|
+
import { Epub, } from "./index.js";
|
|
6
4
|
const GUIDE_TO_EPUBTYPE = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
52
|
-
const XHTML_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
53
|
-
"application/xhtml+xml",
|
|
54
|
-
"application/vnd.adobe-page-template+xml",
|
|
55
|
-
"text/html"
|
|
56
|
-
]);
|
|
57
|
-
const OEB_FONTS = /* @__PURE__ */ new Set([
|
|
58
|
-
"application/vnd.ms-opentype",
|
|
59
|
-
"application/x-font-ttf",
|
|
60
|
-
"application/x-font-otf",
|
|
61
|
-
"application/x-font-truetype",
|
|
62
|
-
"application/font-sfnt",
|
|
63
|
-
"application/font-woff",
|
|
64
|
-
"application/font-woff2",
|
|
65
|
-
"font/woff",
|
|
66
|
-
"font/woff2",
|
|
67
|
-
"font/otf",
|
|
68
|
-
"font/ttf",
|
|
69
|
-
"font/sfnt"
|
|
70
|
-
]);
|
|
71
|
-
const FONT_MIME_BY_EXT = {
|
|
72
|
-
".ttf": "application/font-sfnt",
|
|
73
|
-
".otf": "application/font-sfnt",
|
|
74
|
-
".woff": "application/font-woff",
|
|
75
|
-
".woff2": "font/woff2"
|
|
5
|
+
acknowledgements: "acknowledgments",
|
|
6
|
+
"other.afterword": "afterword",
|
|
7
|
+
"other.appendix": "appendix",
|
|
8
|
+
"other.backmatter": "backmatter",
|
|
9
|
+
bibliography: "bibliography",
|
|
10
|
+
text: "bodymatter",
|
|
11
|
+
"other.chapter": "chapter",
|
|
12
|
+
colophon: "colophon",
|
|
13
|
+
"other.conclusion": "conclusion",
|
|
14
|
+
"other.contributors": "contributors",
|
|
15
|
+
"copyright-page": "copyright-page",
|
|
16
|
+
cover: "cover",
|
|
17
|
+
dedication: "dedication",
|
|
18
|
+
"other.division": "division",
|
|
19
|
+
epigraph: "epigraph",
|
|
20
|
+
"other.epilogue": "epilogue",
|
|
21
|
+
"other.errata": "errata",
|
|
22
|
+
"other.footnotes": "footnotes",
|
|
23
|
+
foreword: "foreword",
|
|
24
|
+
"other.frontmatter": "frontmatter",
|
|
25
|
+
glossary: "glossary",
|
|
26
|
+
"other.halftitlepage": "halftitlepage",
|
|
27
|
+
"other.imprint": "imprint",
|
|
28
|
+
"other.imprimatur": "imprimatur",
|
|
29
|
+
index: "index",
|
|
30
|
+
"other.introduction": "introduction",
|
|
31
|
+
"other.landmarks": "landmarks",
|
|
32
|
+
"other.loa": "loa",
|
|
33
|
+
loi: "loi",
|
|
34
|
+
lot: "lot",
|
|
35
|
+
"other.lov": "lov",
|
|
36
|
+
notes: "",
|
|
37
|
+
"other.notice": "notice",
|
|
38
|
+
"other.other-credits": "other-credits",
|
|
39
|
+
"other.part": "part",
|
|
40
|
+
"other.preamble": "preamble",
|
|
41
|
+
preface: "preface",
|
|
42
|
+
"other.prologue": "prologue",
|
|
43
|
+
"other.rearnotes": "rearnotes",
|
|
44
|
+
"other.subchapter": "subchapter",
|
|
45
|
+
"title-page": "titlepage",
|
|
46
|
+
toc: "toc",
|
|
47
|
+
"other.volume": "volume",
|
|
48
|
+
"other.warning": "warning",
|
|
76
49
|
};
|
|
50
|
+
const XHTML_MEDIA_TYPES = new Set([MediaType.XHTML, MediaType.HTML]);
|
|
51
|
+
// xml helpers
|
|
77
52
|
function getMetadataElement(pkg) {
|
|
78
|
-
|
|
53
|
+
return Epub.findXmlChildByName("metadata", Epub.getXmlChildren(pkg));
|
|
79
54
|
}
|
|
80
55
|
function getManifestElement(pkg) {
|
|
81
|
-
|
|
56
|
+
return Epub.findXmlChildByName("manifest", Epub.getXmlChildren(pkg));
|
|
82
57
|
}
|
|
83
58
|
function getSpineElement(pkg) {
|
|
84
|
-
|
|
59
|
+
return Epub.findXmlChildByName("spine", Epub.getXmlChildren(pkg));
|
|
85
60
|
}
|
|
86
61
|
function ensureId(element) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
62
|
+
const existing = element[":@"]?.["@_id"];
|
|
63
|
+
if (existing)
|
|
64
|
+
return existing;
|
|
65
|
+
const id = `id-${nanoid(8)}`;
|
|
66
|
+
element[":@"] ??= {};
|
|
67
|
+
element[":@"]["@_id"] = id;
|
|
68
|
+
return id;
|
|
94
69
|
}
|
|
95
70
|
function findAllByName(name, xml) {
|
|
96
|
-
|
|
97
|
-
(node) => !Epub.isXmlTextNode(node) && name in node
|
|
98
|
-
);
|
|
71
|
+
return xml.filter((node) => !Epub.isXmlTextNode(node) && name in node);
|
|
99
72
|
}
|
|
100
73
|
function hasElementDeep(xml, name) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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;
|
|
107
83
|
}
|
|
108
84
|
function textContentOf(element) {
|
|
109
|
-
|
|
85
|
+
return Epub.getXhtmlTextContent(Epub.getXmlChildren(element)).trim();
|
|
110
86
|
}
|
|
111
87
|
function removeFromArray(array, item) {
|
|
112
|
-
|
|
113
|
-
|
|
88
|
+
const idx = array.indexOf(item);
|
|
89
|
+
if (idx !== -1)
|
|
90
|
+
array.splice(idx, 1);
|
|
114
91
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
export function upgradeIdentifiers(pkg) {
|
|
94
|
+
const metadata = getMetadataElement(pkg);
|
|
95
|
+
if (!metadata)
|
|
96
|
+
return;
|
|
97
|
+
for (const ident of findAllByName("dc:identifier", metadata.metadata)) {
|
|
98
|
+
const attrs = ident[":@"] ?? {};
|
|
99
|
+
let val = textContentOf(ident);
|
|
100
|
+
let scheme = attrs["@_opf:scheme"];
|
|
101
|
+
if (val.toLowerCase().startsWith("urn:")) {
|
|
102
|
+
const rest = val.slice(4);
|
|
103
|
+
const colonIdx = rest.indexOf(":");
|
|
104
|
+
if (colonIdx > 0) {
|
|
105
|
+
scheme = rest.slice(0, colonIdx);
|
|
106
|
+
val = rest.slice(colonIdx + 1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (scheme && val && !scheme.toLowerCase().startsWith("uri")) {
|
|
110
|
+
val = `${scheme}:${val}`;
|
|
111
|
+
}
|
|
112
|
+
const id = attrs["@_id"];
|
|
113
|
+
ident[":@"] = id ? { "@_id": id } : {};
|
|
114
|
+
Epub.replaceXmlChildren(ident, [Epub.createXmlTextNode(val)]);
|
|
129
115
|
}
|
|
130
|
-
if (scheme && val && !scheme.toLowerCase().startsWith("uri")) {
|
|
131
|
-
val = `${scheme}:${val}`;
|
|
132
|
-
}
|
|
133
|
-
const id = attrs["@_id"];
|
|
134
|
-
ident[":@"] = id ? { "@_id": id } : {};
|
|
135
|
-
Epub.replaceXmlChildren(ident, [Epub.createXmlTextNode(val)]);
|
|
136
|
-
}
|
|
137
116
|
}
|
|
138
|
-
function upgradeTitle(pkg) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
117
|
+
export function upgradeTitle(pkg) {
|
|
118
|
+
const metadata = getMetadataElement(pkg);
|
|
119
|
+
if (!metadata)
|
|
120
|
+
return;
|
|
121
|
+
const titles = findAllByName("dc:title", metadata.metadata);
|
|
122
|
+
let firstTitle = null;
|
|
123
|
+
for (const title of titles) {
|
|
124
|
+
const text = textContentOf(title);
|
|
125
|
+
if (!text) {
|
|
126
|
+
removeFromArray(metadata.metadata, title);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
firstTitle ??= title;
|
|
148
130
|
}
|
|
149
|
-
firstTitle
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
metadata.metadata.push(
|
|
154
|
-
Epub.createXmlElement(
|
|
155
|
-
"meta",
|
|
156
|
-
{ refines: `#${titleId}`, property: "title-type" },
|
|
157
|
-
[Epub.createXmlTextNode("main")]
|
|
158
|
-
)
|
|
159
|
-
);
|
|
131
|
+
if (!firstTitle)
|
|
132
|
+
return;
|
|
133
|
+
const titleId = ensureId(firstTitle);
|
|
134
|
+
metadata.metadata.push(Epub.createXmlElement("meta", { refines: `#${titleId}`, property: "title-type" }, [Epub.createXmlTextNode("main")]));
|
|
160
135
|
}
|
|
161
|
-
function upgradeLanguages(pkg) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
136
|
+
export function upgradeLanguages(pkg) {
|
|
137
|
+
const metadata = getMetadataElement(pkg);
|
|
138
|
+
if (!metadata)
|
|
139
|
+
return;
|
|
140
|
+
const langs = findAllByName("dc:language", metadata.metadata);
|
|
141
|
+
if (langs.length > 0) {
|
|
142
|
+
for (const lang of langs) {
|
|
143
|
+
const id = lang[":@"]?.["@_id"];
|
|
144
|
+
lang[":@"] = id ? { "@_id": id } : {};
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
170
147
|
}
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
metadata.metadata.push(
|
|
174
|
-
Epub.createXmlElement("dc:language", {}, [Epub.createXmlTextNode("und")])
|
|
175
|
-
);
|
|
148
|
+
metadata.metadata.push(Epub.createXmlElement("dc:language", {}, [Epub.createXmlTextNode("und")]));
|
|
176
149
|
}
|
|
177
|
-
function upgradeAuthors(pkg) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
for (const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (sort) {
|
|
201
|
-
metadata.metadata.push(
|
|
202
|
-
Epub.createXmlElement(
|
|
203
|
-
"meta",
|
|
204
|
-
{ refines: `#${elemId}`, property: "file-as" },
|
|
205
|
-
[Epub.createXmlTextNode(sort)]
|
|
206
|
-
)
|
|
207
|
-
);
|
|
208
|
-
}
|
|
150
|
+
export function upgradeAuthors(pkg) {
|
|
151
|
+
const metadata = getMetadataElement(pkg);
|
|
152
|
+
if (!metadata)
|
|
153
|
+
return;
|
|
154
|
+
for (const which of ["dc:creator", "dc:contributor"]) {
|
|
155
|
+
for (const elem of findAllByName(which, metadata.metadata)) {
|
|
156
|
+
const attrs = elem[":@"] ?? {};
|
|
157
|
+
const role = attrs["@_opf:role"];
|
|
158
|
+
const sort = attrs["@_opf:file-as"];
|
|
159
|
+
const elemId = role || sort ? ensureId(elem) : attrs["@_id"];
|
|
160
|
+
// strip all non-id attributes
|
|
161
|
+
elem[":@"] = elemId ? { "@_id": elemId } : {};
|
|
162
|
+
if (role) {
|
|
163
|
+
metadata.metadata.push(Epub.createXmlElement("meta", {
|
|
164
|
+
refines: `#${elemId}`,
|
|
165
|
+
property: "role",
|
|
166
|
+
scheme: "marc:relators",
|
|
167
|
+
}, [Epub.createXmlTextNode(role)]));
|
|
168
|
+
}
|
|
169
|
+
if (sort) {
|
|
170
|
+
metadata.metadata.push(Epub.createXmlElement("meta", { refines: `#${elemId}`, property: "file-as" }, [Epub.createXmlTextNode(sort)]));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
209
173
|
}
|
|
210
|
-
}
|
|
211
174
|
}
|
|
212
|
-
function upgradeDate(pkg) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
175
|
+
export function upgradeDate(pkg) {
|
|
176
|
+
const metadata = getMetadataElement(pkg);
|
|
177
|
+
if (!metadata)
|
|
178
|
+
return;
|
|
179
|
+
const dates = findAllByName("dc:date", metadata.metadata);
|
|
180
|
+
let kept = false;
|
|
181
|
+
for (const date of dates) {
|
|
182
|
+
const text = textContentOf(date);
|
|
183
|
+
if (!text || kept) {
|
|
184
|
+
removeFromArray(metadata.metadata, date);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
kept = true;
|
|
188
|
+
const id = date[":@"]?.["@_id"];
|
|
189
|
+
date[":@"] = id ? { "@_id": id } : {};
|
|
223
190
|
}
|
|
224
|
-
kept = true;
|
|
225
|
-
const id = (_a = date[":@"]) == null ? void 0 : _a["@_id"];
|
|
226
|
-
date[":@"] = id ? { "@_id": id } : {};
|
|
227
|
-
}
|
|
228
191
|
}
|
|
229
|
-
function upgradeMeta(pkg) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
192
|
+
export function upgradeMeta(pkg) {
|
|
193
|
+
const metadata = getMetadataElement(pkg);
|
|
194
|
+
if (!metadata)
|
|
195
|
+
return;
|
|
196
|
+
for (const meta of findAllByName("meta", metadata.metadata)) {
|
|
197
|
+
const attrs = meta[":@"] ?? {};
|
|
198
|
+
const name = attrs["@_name"] ?? "";
|
|
199
|
+
const content = attrs["@_content"] ?? "";
|
|
200
|
+
let prop = null;
|
|
201
|
+
let value = content;
|
|
202
|
+
const cleanName = name.startsWith("rendition:")
|
|
203
|
+
? name.slice("rendition:".length)
|
|
204
|
+
: name;
|
|
205
|
+
if (["orientation", "layout", "spread"].includes(cleanName)) {
|
|
206
|
+
prop = `rendition:${cleanName}`;
|
|
207
|
+
}
|
|
208
|
+
else if (name === "fixed-layout") {
|
|
209
|
+
prop = "rendition:layout";
|
|
210
|
+
value = content.toLowerCase() === "true" ? "pre-paginated" : "reflowable";
|
|
211
|
+
}
|
|
212
|
+
else if (name === "orientation-lock") {
|
|
213
|
+
prop = "rendition:orientation";
|
|
214
|
+
const map = {
|
|
215
|
+
portrait: "portrait",
|
|
216
|
+
landscape: "landscape",
|
|
217
|
+
};
|
|
218
|
+
value = map[content.toLowerCase()] ?? "auto";
|
|
219
|
+
}
|
|
220
|
+
if (!prop)
|
|
221
|
+
continue;
|
|
222
|
+
delete attrs["@_name"];
|
|
223
|
+
delete attrs["@_content"];
|
|
224
|
+
attrs["@_property"] = prop;
|
|
225
|
+
Epub.replaceXmlChildren(meta, [Epub.createXmlTextNode(value)]);
|
|
251
226
|
}
|
|
252
|
-
if (!prop) continue;
|
|
253
|
-
delete attrs["@_name"];
|
|
254
|
-
delete attrs["@_content"];
|
|
255
|
-
attrs["@_property"] = prop;
|
|
256
|
-
Epub.replaceXmlChildren(meta, [Epub.createXmlTextNode(value)]);
|
|
257
|
-
}
|
|
258
227
|
}
|
|
259
|
-
function upgradeCover(pkg) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
228
|
+
export function upgradeCover(pkg) {
|
|
229
|
+
const metadata = getMetadataElement(pkg);
|
|
230
|
+
const manifest = getManifestElement(pkg);
|
|
231
|
+
if (!metadata || !manifest)
|
|
232
|
+
return;
|
|
233
|
+
for (const meta of findAllByName("meta", metadata.metadata)) {
|
|
234
|
+
const attrs = meta[":@"];
|
|
235
|
+
const isCoverMeta = attrs?.["@_name"] === "cover" && attrs["@_content"];
|
|
236
|
+
if (!isCoverMeta)
|
|
237
|
+
continue;
|
|
238
|
+
const itemId = attrs["@_content"];
|
|
239
|
+
for (const item of findAllByName("item", manifest.manifest)) {
|
|
240
|
+
const itemAttrs = item[":@"];
|
|
241
|
+
if (!itemAttrs || itemAttrs["@_id"] !== itemId)
|
|
242
|
+
continue;
|
|
243
|
+
const mediaType = (itemAttrs["@_media-type"] ?? "").toLowerCase();
|
|
244
|
+
// only mark actual images, not html wrappers
|
|
245
|
+
const isImage = mediaType && !mediaType.includes("xml") && !mediaType.includes("html");
|
|
246
|
+
if (!isImage)
|
|
247
|
+
continue;
|
|
248
|
+
const existing = (itemAttrs["@_properties"] ?? "")
|
|
249
|
+
.split(" ")
|
|
250
|
+
.filter(Boolean);
|
|
251
|
+
const props = new Set(existing);
|
|
252
|
+
props.add("cover-image");
|
|
253
|
+
itemAttrs["@_properties"] = [...props].sort().join(" ");
|
|
254
|
+
}
|
|
278
255
|
}
|
|
279
|
-
}
|
|
280
256
|
}
|
|
281
|
-
function removeInvalidDcAttrs(pkg) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
257
|
+
export function removeInvalidDcAttrs(pkg) {
|
|
258
|
+
const metadata = getMetadataElement(pkg);
|
|
259
|
+
if (!metadata)
|
|
260
|
+
return;
|
|
261
|
+
for (const node of metadata.metadata) {
|
|
262
|
+
if (Epub.isXmlTextNode(node))
|
|
263
|
+
continue;
|
|
264
|
+
const name = Epub.getXmlElementName(node);
|
|
265
|
+
if (!name.startsWith("dc:"))
|
|
266
|
+
continue;
|
|
267
|
+
const attrs = node[":@"];
|
|
268
|
+
if (!attrs)
|
|
269
|
+
continue;
|
|
270
|
+
const id = attrs["@_id"];
|
|
271
|
+
node[":@"] = id ? { "@_id": id } : {};
|
|
272
|
+
}
|
|
293
273
|
}
|
|
294
|
-
function setLastModified(pkg) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
274
|
+
export function setLastModified(pkg) {
|
|
275
|
+
const metadata = getMetadataElement(pkg);
|
|
276
|
+
if (!metadata)
|
|
277
|
+
return;
|
|
278
|
+
for (let i = metadata.metadata.length - 1; i >= 0; i--) {
|
|
279
|
+
const node = metadata.metadata[i];
|
|
280
|
+
if (!node || Epub.isXmlTextNode(node))
|
|
281
|
+
continue;
|
|
282
|
+
if (node[":@"]?.["@_property"] === "dcterms:modified") {
|
|
283
|
+
metadata.metadata.splice(i, 1);
|
|
284
|
+
}
|
|
303
285
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
Epub.createXmlTextNode(now)
|
|
309
|
-
])
|
|
310
|
-
);
|
|
286
|
+
const now = new Date().toISOString().replace(/\.\d+/, "");
|
|
287
|
+
metadata.metadata.push(Epub.createXmlElement("meta", { property: "dcterms:modified" }, [
|
|
288
|
+
Epub.createXmlTextNode(now),
|
|
289
|
+
]));
|
|
311
290
|
}
|
|
312
|
-
function upgradePackageMetadata(pkg) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
291
|
+
export function upgradePackageMetadata(pkg) {
|
|
292
|
+
upgradeIdentifiers(pkg);
|
|
293
|
+
upgradeTitle(pkg);
|
|
294
|
+
upgradeLanguages(pkg);
|
|
295
|
+
upgradeAuthors(pkg);
|
|
296
|
+
upgradeDate(pkg);
|
|
297
|
+
upgradeMeta(pkg);
|
|
298
|
+
upgradeCover(pkg);
|
|
299
|
+
removeInvalidDcAttrs(pkg);
|
|
300
|
+
setLastModified(pkg);
|
|
322
301
|
}
|
|
323
|
-
function extractGuideLandmarks(pkg) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
302
|
+
export function extractGuideLandmarks(pkg) {
|
|
303
|
+
const guide = Epub.findXmlChildByName("guide", Epub.getXmlChildren(pkg));
|
|
304
|
+
if (!guide)
|
|
305
|
+
return [];
|
|
306
|
+
const landmarks = [];
|
|
307
|
+
for (const node of Epub.getXmlChildren(guide)) {
|
|
308
|
+
if (Epub.isXmlTextNode(node))
|
|
309
|
+
continue;
|
|
310
|
+
if (!("reference" in node))
|
|
311
|
+
continue;
|
|
312
|
+
const attrs = node[":@"] ?? {};
|
|
313
|
+
const href = attrs["@_href"] ?? "";
|
|
314
|
+
const title = attrs["@_title"] ?? "";
|
|
315
|
+
const guideType = (attrs["@_type"] ?? "").toLowerCase();
|
|
316
|
+
const epubType = GUIDE_TO_EPUBTYPE[guideType];
|
|
317
|
+
if (epubType === undefined || !href)
|
|
318
|
+
continue;
|
|
319
|
+
landmarks.push({ href, title, type: epubType });
|
|
320
|
+
}
|
|
321
|
+
return landmarks;
|
|
339
322
|
}
|
|
340
|
-
function removeGuide(pkg) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
323
|
+
export function removeGuide(pkg) {
|
|
324
|
+
const children = Epub.getXmlChildren(pkg);
|
|
325
|
+
const guide = Epub.findXmlChildByName("guide", children);
|
|
326
|
+
if (guide) {
|
|
327
|
+
removeFromArray(children, guide);
|
|
328
|
+
}
|
|
346
329
|
}
|
|
347
|
-
function removeSpineTocRef(pkg) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
330
|
+
export function removeSpineTocRef(pkg) {
|
|
331
|
+
const spine = getSpineElement(pkg);
|
|
332
|
+
if (!spine?.[":@"])
|
|
333
|
+
return;
|
|
334
|
+
delete spine[":@"]["@_toc"];
|
|
351
335
|
}
|
|
352
|
-
async function collectManifestProperties(epub) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
336
|
+
export async function collectManifestProperties(epub) {
|
|
337
|
+
const manifest = await epub.getManifest();
|
|
338
|
+
for (const item of Object.values(manifest)) {
|
|
339
|
+
const mediaType = MediaType.fromMime(item.mediaType ?? "");
|
|
340
|
+
if (!mediaType || !XHTML_MEDIA_TYPES.has(mediaType))
|
|
341
|
+
continue;
|
|
342
|
+
let xml;
|
|
343
|
+
try {
|
|
344
|
+
xml = await epub.readXhtmlItemContents(item.id);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const props = new Set(item.properties ?? []);
|
|
350
|
+
const before = props.size;
|
|
351
|
+
if (hasElementDeep(xml, "svg"))
|
|
352
|
+
props.add("svg");
|
|
353
|
+
if (hasElementDeep(xml, "script"))
|
|
354
|
+
props.add("scripted");
|
|
355
|
+
if (hasElementDeep(xml, "math"))
|
|
356
|
+
props.add("mathml");
|
|
357
|
+
if (hasElementDeep(xml, "epub:switch"))
|
|
358
|
+
props.add("switch");
|
|
359
|
+
if (props.size === before)
|
|
360
|
+
continue;
|
|
361
|
+
await epub.updateManifestItem(item.id, {
|
|
362
|
+
...item,
|
|
363
|
+
properties: [...props].sort(),
|
|
364
|
+
});
|
|
363
365
|
}
|
|
364
|
-
const props = new Set(item.properties ?? []);
|
|
365
|
-
const before = props.size;
|
|
366
|
-
if (hasElementDeep(xml, "svg")) props.add("svg");
|
|
367
|
-
if (hasElementDeep(xml, "script")) props.add("scripted");
|
|
368
|
-
if (hasElementDeep(xml, "math")) props.add("mathml");
|
|
369
|
-
if (hasElementDeep(xml, "epub:switch")) props.add("switch");
|
|
370
|
-
if (props.size === before) continue;
|
|
371
|
-
await epub.updateManifestItem(item.id, {
|
|
372
|
-
...item,
|
|
373
|
-
properties: [...props].sort()
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
366
|
}
|
|
377
|
-
function fixFontMimeTypes(pkg) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
item[":@"] ??= {};
|
|
391
|
-
item[":@"]["@_media-type"] = corrected;
|
|
367
|
+
export function fixFontMimeTypes(pkg) {
|
|
368
|
+
const manifest = getManifestElement(pkg);
|
|
369
|
+
if (!manifest)
|
|
370
|
+
return;
|
|
371
|
+
for (const item of findAllByName("item", manifest.manifest)) {
|
|
372
|
+
const declared = item[":@"]?.["@_media-type"] ?? "";
|
|
373
|
+
if (!MediaType.fromMime(declared)?.is("font"))
|
|
374
|
+
continue;
|
|
375
|
+
const corrected = MediaType.fromPath(item[":@"]?.["@_href"] ?? "");
|
|
376
|
+
if (!corrected?.is("font") || corrected.mime === declared)
|
|
377
|
+
continue;
|
|
378
|
+
item[":@"] ??= {};
|
|
379
|
+
item[":@"]["@_media-type"] = corrected.mime;
|
|
392
380
|
}
|
|
393
|
-
}
|
|
394
381
|
}
|
|
395
|
-
function buildTocOl(entries) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
382
|
+
export function buildTocOl(entries) {
|
|
383
|
+
const children = entries.map((entry) => {
|
|
384
|
+
const label = entry.title.replace(/\s+/g, " ").trim();
|
|
385
|
+
const liChildren = [];
|
|
386
|
+
if (entry.href) {
|
|
387
|
+
liChildren.push(Epub.createXmlElement("a", { href: entry.href }, [
|
|
388
|
+
Epub.createXmlTextNode(label),
|
|
389
|
+
]));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
liChildren.push(Epub.createXmlElement("span", {}, [Epub.createXmlTextNode(label)]));
|
|
393
|
+
}
|
|
394
|
+
if (entry.children && entry.children.length > 0) {
|
|
395
|
+
liChildren.push(buildTocOl(entry.children));
|
|
396
|
+
}
|
|
397
|
+
return Epub.createXmlElement("li", {}, liChildren);
|
|
398
|
+
});
|
|
399
|
+
return Epub.createXmlElement("ol", {}, children);
|
|
400
|
+
}
|
|
401
|
+
export async function buildNavDocument(epub, tocEntries, landmarks) {
|
|
402
|
+
let tocOl;
|
|
403
|
+
if (tocEntries.length > 0) {
|
|
404
|
+
tocOl = buildTocOl(tocEntries);
|
|
409
405
|
}
|
|
410
|
-
|
|
411
|
-
|
|
406
|
+
else {
|
|
407
|
+
// epub 3 requires a non-empty <ol>, fall back to first spine item
|
|
408
|
+
const spineItems = await epub.getSpineItems();
|
|
409
|
+
const firstHref = spineItems[0]?.href ?? "#";
|
|
410
|
+
tocOl = Epub.createXmlElement("ol", {}, [
|
|
411
|
+
Epub.createXmlElement("li", {}, [
|
|
412
|
+
Epub.createXmlElement("a", { href: firstHref }, [
|
|
413
|
+
Epub.createXmlTextNode("Start"),
|
|
414
|
+
]),
|
|
415
|
+
]),
|
|
416
|
+
]);
|
|
412
417
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
var _a;
|
|
419
|
-
let tocOl;
|
|
420
|
-
if (tocEntries.length > 0) {
|
|
421
|
-
tocOl = buildTocOl(tocEntries);
|
|
422
|
-
} else {
|
|
423
|
-
const spineItems = await epub.getSpineItems();
|
|
424
|
-
const firstHref = ((_a = spineItems[0]) == null ? void 0 : _a.href) ?? "#";
|
|
425
|
-
tocOl = Epub.createXmlElement("ol", {}, [
|
|
426
|
-
Epub.createXmlElement("li", {}, [
|
|
427
|
-
Epub.createXmlElement("a", { href: firstHref }, [
|
|
428
|
-
Epub.createXmlTextNode("Start")
|
|
429
|
-
])
|
|
430
|
-
])
|
|
418
|
+
const tocNav = Epub.createXmlElement("nav", { "epub:type": "toc" }, [
|
|
419
|
+
Epub.createXmlElement("h1", {}, [
|
|
420
|
+
Epub.createXmlTextNode("Table of Contents"),
|
|
421
|
+
]),
|
|
422
|
+
tocOl,
|
|
431
423
|
]);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
])
|
|
450
|
-
])
|
|
451
|
-
)
|
|
452
|
-
);
|
|
453
|
-
body.push(
|
|
454
|
-
Epub.createXmlElement("nav", { "epub:type": "landmarks", hidden: "" }, [
|
|
455
|
-
lmOl
|
|
456
|
-
])
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
const head = [
|
|
460
|
-
Epub.createXmlElement("title", {}, [Epub.createXmlTextNode("Navigation")])
|
|
461
|
-
];
|
|
462
|
-
const navDoc = await epub.createXhtmlDocument(body, head);
|
|
463
|
-
return Epub.xhtmlBuilder.build(navDoc);
|
|
424
|
+
const body = [tocNav];
|
|
425
|
+
const validLandmarks = landmarks.filter((lm) => lm.type);
|
|
426
|
+
if (validLandmarks.length > 0) {
|
|
427
|
+
const lmOl = Epub.createXmlElement("ol", {}, validLandmarks.map((lm) => Epub.createXmlElement("li", {}, [
|
|
428
|
+
Epub.createXmlElement("a", { "epub:type": lm.type, href: lm.href }, [
|
|
429
|
+
Epub.createXmlTextNode(lm.title || lm.type),
|
|
430
|
+
]),
|
|
431
|
+
])));
|
|
432
|
+
body.push(Epub.createXmlElement("nav", { "epub:type": "landmarks", hidden: "" }, [
|
|
433
|
+
lmOl,
|
|
434
|
+
]));
|
|
435
|
+
}
|
|
436
|
+
const head = [
|
|
437
|
+
Epub.createXmlElement("title", {}, [Epub.createXmlTextNode("Navigation")]),
|
|
438
|
+
];
|
|
439
|
+
const navDoc = await epub.createXhtmlDocument(body, head);
|
|
440
|
+
return Epub.xhtmlBuilder.build(navDoc);
|
|
464
441
|
}
|
|
465
|
-
function setPackageVersion(pkg, version) {
|
|
466
|
-
|
|
467
|
-
|
|
442
|
+
export function setPackageVersion(pkg, version) {
|
|
443
|
+
pkg[":@"] ??= {};
|
|
444
|
+
pkg[":@"]["@_version"] = version;
|
|
468
445
|
}
|
|
469
|
-
async function chooseNavHref(epub) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
return candidate;
|
|
446
|
+
export async function chooseNavHref(epub) {
|
|
447
|
+
const manifest = await epub.getManifest();
|
|
448
|
+
const existingHrefs = new Set(Object.values(manifest).map((item) => item.href));
|
|
449
|
+
let candidate = "nav.xhtml";
|
|
450
|
+
let i = 1;
|
|
451
|
+
while (existingHrefs.has(candidate)) {
|
|
452
|
+
candidate = `nav${i}.xhtml`;
|
|
453
|
+
i++;
|
|
454
|
+
}
|
|
455
|
+
return candidate;
|
|
481
456
|
}
|
|
482
|
-
async function removeNcx(epub) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
(
|
|
486
|
-
|
|
487
|
-
return ((_a = item.mediaType) == null ? void 0 : _a.toLowerCase()) === "application/x-dtbncx+xml";
|
|
457
|
+
export async function removeNcx(epub) {
|
|
458
|
+
const manifest = await epub.getManifest();
|
|
459
|
+
const ncxItem = Object.values(manifest).find((item) => MediaType.fromMime(item.mediaType ?? "") === MediaType.NCX);
|
|
460
|
+
if (ncxItem) {
|
|
461
|
+
await epub.removeManifestItem(ncxItem.id);
|
|
488
462
|
}
|
|
489
|
-
);
|
|
490
|
-
if (ncxItem) {
|
|
491
|
-
await epub.removeManifestItem(ncxItem.id);
|
|
492
|
-
}
|
|
493
463
|
}
|
|
494
|
-
export {
|
|
495
|
-
buildNavDocument,
|
|
496
|
-
buildTocOl,
|
|
497
|
-
chooseNavHref,
|
|
498
|
-
collectManifestProperties,
|
|
499
|
-
extractGuideLandmarks,
|
|
500
|
-
fixFontMimeTypes,
|
|
501
|
-
removeGuide,
|
|
502
|
-
removeInvalidDcAttrs,
|
|
503
|
-
removeNcx,
|
|
504
|
-
removeSpineTocRef,
|
|
505
|
-
setLastModified,
|
|
506
|
-
setPackageVersion,
|
|
507
|
-
upgradeAuthors,
|
|
508
|
-
upgradeCover,
|
|
509
|
-
upgradeDate,
|
|
510
|
-
upgradeIdentifiers,
|
|
511
|
-
upgradeLanguages,
|
|
512
|
-
upgradeMeta,
|
|
513
|
-
upgradePackageMetadata,
|
|
514
|
-
upgradeTitle
|
|
515
|
-
};
|