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