@storyteller-platform/epub 0.4.10 → 0.5.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/dist/index.js CHANGED
@@ -1,48 +1,7 @@
1
- var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
2
- var __typeError = (msg) => {
3
- throw TypeError(msg);
4
- };
5
- var __using = (stack, value, async) => {
6
- if (value != null) {
7
- if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
8
- var dispose, inner;
9
- if (async) dispose = value[__knownSymbol("asyncDispose")];
10
- if (dispose === void 0) {
11
- dispose = value[__knownSymbol("dispose")];
12
- if (async) inner = dispose;
13
- }
14
- if (typeof dispose !== "function") __typeError("Object not disposable");
15
- if (inner) dispose = function() {
16
- try {
17
- inner.call(this);
18
- } catch (e) {
19
- return Promise.reject(e);
20
- }
21
- };
22
- stack.push([async, dispose, value]);
23
- } else if (async) {
24
- stack.push([async]);
25
- }
26
- return value;
27
- };
28
- var __callDispose = (stack, error, hasError) => {
29
- var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
30
- return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
31
- };
32
- var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
33
- var next = (it) => {
34
- while (it = stack.pop()) {
35
- try {
36
- var result = it[1] && it[1].call(it[2]);
37
- if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
38
- } catch (e) {
39
- fail(e);
40
- }
41
- }
42
- if (hasError) throw error;
43
- };
44
- return next();
45
- };
1
+ import {
2
+ __callDispose,
3
+ __using
4
+ } from "./chunk-BIEQXUOY.js";
46
5
  import { randomUUID } from "node:crypto";
47
6
  import { createWriteStream, rmSync } from "node:fs";
48
7
  import { cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
@@ -56,6 +15,7 @@ import { nanoid } from "nanoid";
56
15
  import { fromBuffer, open } from "yauzl-promise";
57
16
  import { ZipFile } from "yazl";
58
17
  import { dirname, join, resolve } from "@storyteller-platform/path";
18
+ import * as Upgrade from "./upgrade.js";
59
19
  const MP3_FILE_EXTENSIONS = [".mp3"];
60
20
  const MPEG4_FILE_EXTENSIONS = [".mp4", ".m4a", ".m4b"];
61
21
  const AAC_FILE_EXTENSIONS = [".aac"];
@@ -81,6 +41,8 @@ const AUDIO_FILE_EXTENSIONS = [
81
41
  function isAudioFile(filenameOrExt) {
82
42
  return AUDIO_FILE_EXTENSIONS.some((ext) => filenameOrExt.endsWith(ext));
83
43
  }
44
+ class EpubVersionError extends Error {
45
+ }
84
46
  class Epub {
85
47
  constructor(extractPath, inputPath) {
86
48
  this.extractPath = extractPath;
@@ -245,9 +207,29 @@ ${JSON.stringify(element, null, 2)}`
245
207
  * the provided name and optional filter.
246
208
  */
247
209
  static findXmlChildByName(name, xml, filter) {
248
- const element = xml.find((e) => name in e && (filter ? filter(e) : true));
210
+ const element = xml.find(
211
+ (e) => name in e && (filter ? filter(e) : true)
212
+ );
249
213
  return element;
250
214
  }
215
+ /**
216
+ * Given an XML structure, find the first descendant matching
217
+ * the provided name and optional filter.
218
+ *
219
+ * Will perform a breadth first search for the element, returning
220
+ * the highest element in the tree matching the name and filter.
221
+ */
222
+ static findXmlDescendantByName(name, xml, filter) {
223
+ const found = Epub.findXmlChildByName(name, xml, filter);
224
+ if (found) return found;
225
+ for (const node of xml) {
226
+ if (Epub.isXmlTextNode(node)) continue;
227
+ const children = Epub.getXmlChildren(node);
228
+ const found2 = this.findXmlDescendantByName(name, children, filter);
229
+ if (found2) return found2;
230
+ }
231
+ return void 0;
232
+ }
251
233
  /**
252
234
  * Given an XMLNode, determine whether it represents
253
235
  * a text node or an XML element.
@@ -338,9 +320,21 @@ ${JSON.stringify(element, null, 2)}`
338
320
  * path to an EPUB file on disk, or a Uint8Array representing
339
321
  * the data of the EPUB publication.
340
322
  */
341
- // eslint-disable-next-line @typescript-eslint/require-await
342
323
  static async from(pathOrData) {
343
- var _a, _b;
324
+ const epub = await this.open(pathOrData);
325
+ const version = await epub.getVersion();
326
+ if (!version.startsWith("3.")) {
327
+ epub.discardAndClose();
328
+ throw new EpubVersionError(
329
+ "This is not a valid EPUB 3 publication. This library only supports EPUB 3, not EPUB 2. Use Epub.upgrade(path) to convert."
330
+ );
331
+ }
332
+ return epub;
333
+ }
334
+ /**
335
+ * Open an EPUB publication and return an Epub instance.
336
+ */
337
+ static async open(pathOrData) {
344
338
  const extractPath = join(
345
339
  tmpdir(),
346
340
  `storyteller-platform-epub-${randomUUID()}.epub`
@@ -383,13 +377,7 @@ ${JSON.stringify(element, null, 2)}`
383
377
  epub.discardAndClose();
384
378
  console.error(e);
385
379
  throw new Error(
386
- "This is not a valid EPUB 3 publication. This library only support EPUB 3, not EPUB 2. Try using an automatic conversion tool to convert this publication to EPUB 3."
387
- );
388
- }
389
- const packageEl = await epub.getPackageElement();
390
- if (!((_b = (_a = packageEl[":@"]) == null ? void 0 : _a["@_version"]) == null ? void 0 : _b.startsWith("3."))) {
391
- throw new Error(
392
- "This is not a valid EPUB 3 publication. This library only support EPUB 3, not EPUB 2. Try using an automatic conversion tool to convert this publication to EPUB 3."
380
+ "This is not a valid EPUB publication. Could not read the package document."
393
381
  );
394
382
  }
395
383
  return epub;
@@ -409,7 +397,7 @@ ${JSON.stringify(element, null, 2)}`
409
397
  }
410
398
  async removeEntry(href) {
411
399
  const rootfile = await this.getRootfile();
412
- const filename = this.resolveHref(rootfile, href);
400
+ const filename = this.resolveInternalHref(rootfile, href);
413
401
  await rm(filename);
414
402
  }
415
403
  async getFileData(path, encoding) {
@@ -782,6 +770,57 @@ ${JSON.stringify(element, null, 2)}`
782
770
  value: date.toISOString()
783
771
  });
784
772
  }
773
+ /**
774
+ * Retrieve the modified date from the dcterms:modified metadata
775
+ * in the EPUB metadata as a Date object.
776
+ *
777
+ * If there is no meta element with dcterms:modified, returns null.
778
+ *
779
+ * @link https://www.w3.org/TR/epub-33/#sec-metadata-last-modified
780
+ */
781
+ async getModifiedDate() {
782
+ const metadata = await this.getMetadata();
783
+ const entry = metadata.find(
784
+ ({ properties }) => properties["property"] === "dcterms:modified"
785
+ );
786
+ if (!(entry == null ? void 0 : entry.value)) return null;
787
+ return new Date(entry.value);
788
+ }
789
+ /**
790
+ * Retrieve the layout from the rendition:layout meta element
791
+ * in the EPUB metadata.
792
+ *
793
+ * If there is no meta element, returns 'reflowable'.
794
+ *
795
+ * @link https://www.w3.org/TR/epub-33/#layout
796
+ */
797
+ async getLayout() {
798
+ const metadata = await this.getMetadata();
799
+ const entry = metadata.find(
800
+ ({ properties }) => properties["property"] === "rendition:layout"
801
+ );
802
+ if ((entry == null ? void 0 : entry.value) !== "reflowable" && (entry == null ? void 0 : entry.value) !== "pre-paginated") {
803
+ return "reflowable";
804
+ }
805
+ return entry.value;
806
+ }
807
+ /**
808
+ * Retrieve the base direction from the package element.
809
+ *
810
+ * If there is no `dir` attribute on the package element,
811
+ * returns 'auto'.
812
+ *
813
+ * @link https://www.w3.org/TR/epub-33/#attrdef-dir
814
+ */
815
+ async getBaseDirection() {
816
+ var _a;
817
+ const packageEl = await this.getPackageElement();
818
+ const dir = (_a = packageEl[":@"]) == null ? void 0 : _a["@_dir"];
819
+ if (dir !== "ltr" && dir !== "rtl" && dir !== "auto") {
820
+ return "auto";
821
+ }
822
+ return dir;
823
+ }
785
824
  /**
786
825
  * Set the dc:type metadata element.
787
826
  *
@@ -1562,17 +1601,135 @@ ${JSON.stringify(element, null, 2)}`
1562
1601
  });
1563
1602
  this.spine = null;
1564
1603
  }
1604
+ async getNavigationChildren(ol, navHref, { resolveToRoot } = {}) {
1605
+ var _a;
1606
+ const children = [];
1607
+ const childrenElements = Epub.getXmlChildren(ol).filter(
1608
+ (node) => !Epub.isXmlTextNode(node) && Epub.getXmlElementName(node) === "li"
1609
+ );
1610
+ for (const childEl of childrenElements) {
1611
+ const [firstChild, secondChild] = Epub.getXmlChildren(childEl).filter(
1612
+ (node) => !Epub.isXmlTextNode(node) && ["a", "span", "ol"].includes(Epub.getXmlElementName(node))
1613
+ );
1614
+ if (!firstChild) continue;
1615
+ if (!["a", "span"].includes(Epub.getXmlElementName(firstChild))) {
1616
+ continue;
1617
+ }
1618
+ if (Epub.getXmlElementName(firstChild) === "span" && (!secondChild || Epub.getXmlElementName(secondChild) !== "ol")) {
1619
+ continue;
1620
+ }
1621
+ children.push({
1622
+ title: Epub.getXhtmlTextContent(Epub.getXmlChildren(firstChild)),
1623
+ ...Epub.getXmlElementName(firstChild) === "a" && ((_a = firstChild[":@"]) == null ? void 0 : _a["@_href"]) && {
1624
+ href: await this.resolveHref(
1625
+ firstChild[":@"]["@_href"],
1626
+ void 0,
1627
+ { toRoot: resolveToRoot }
1628
+ )
1629
+ },
1630
+ ...secondChild && Epub.getXmlElementName(secondChild) === "ol" && {
1631
+ children: await this.getNavigationChildren(secondChild, navHref, {
1632
+ resolveToRoot
1633
+ })
1634
+ }
1635
+ });
1636
+ }
1637
+ return children;
1638
+ }
1639
+ async getNavigation(role, { resolveToRoot } = {}) {
1640
+ const manifest = await this.getManifest();
1641
+ const navItem = Object.values(manifest).find(
1642
+ (item) => {
1643
+ var _a;
1644
+ return (_a = item.properties) == null ? void 0 : _a.includes("nav");
1645
+ }
1646
+ );
1647
+ if (!navItem) return null;
1648
+ const navContents = await this.readXhtmlItemContents(navItem.id);
1649
+ const navEl = Epub.findXmlDescendantByName(
1650
+ "nav",
1651
+ navContents,
1652
+ (node) => Epub.getXmlAttributes(node)["epub:type"] === role
1653
+ );
1654
+ if (!navEl) return null;
1655
+ const [firstChild, secondChild] = Epub.getXmlChildren(navEl).filter(
1656
+ (node) => !!(!Epub.isXmlTextNode(node) && Epub.getXmlElementName(node).match(/(?:h[1-6]|ol)/))
1657
+ );
1658
+ if (!firstChild) return null;
1659
+ const title = Epub.getXmlElementName(firstChild).match(/h[1-6]/) ? Epub.getXhtmlTextContent(Epub.getXmlChildren(firstChild)) : null;
1660
+ const list = Epub.getXmlElementName(firstChild) === "ol" ? firstChild : secondChild && Epub.getXmlElementName(secondChild) === "ol" ? secondChild : null;
1661
+ if (!list) return null;
1662
+ const children = await this.getNavigationChildren(list, navItem.href, {
1663
+ resolveToRoot
1664
+ });
1665
+ return {
1666
+ ...title && { title },
1667
+ children
1668
+ };
1669
+ }
1670
+ /**
1671
+ * Returns the structured table of contents navigation document
1672
+ * as a Navigation object.
1673
+ *
1674
+ * @link https://www.w3.org/TR/epub-33/#sec-nav-toc
1675
+ */
1676
+ async getTableOfContents({
1677
+ resolveToRoot
1678
+ } = {}) {
1679
+ const navigationToc = await this.getNavigation("toc", { resolveToRoot });
1680
+ if (navigationToc) return navigationToc;
1681
+ const ncxToc = await this.getNcxTableOfContents();
1682
+ return {
1683
+ children: ncxToc
1684
+ };
1685
+ }
1686
+ /**
1687
+ * Returns the structured landmarks navigation document
1688
+ * as a Navigation object
1689
+ *
1690
+ * @link https://www.w3.org/TR/epub-33/#sec-nav-landmarks
1691
+ */
1692
+ async getLandmarks({
1693
+ resolveToRoot
1694
+ } = {}) {
1695
+ return this.getNavigation("landmarks", { resolveToRoot });
1696
+ }
1697
+ /**
1698
+ * Returns the structured page list navigation document
1699
+ * as a Navigation object
1700
+ *
1701
+ * @link https://www.w3.org/TR/epub-33/#sec-nav-landmarks
1702
+ */
1703
+ async getPageList({
1704
+ resolveToRoot
1705
+ } = {}) {
1706
+ return this.getNavigation("page-list", { resolveToRoot });
1707
+ }
1565
1708
  /**
1566
1709
  * Returns a Zip Entry path for an HREF
1567
1710
  */
1568
- resolveHref(from, href) {
1711
+ resolveInternalHref(from, href) {
1569
1712
  const startPath = dirname(from);
1570
1713
  return resolve(this.extractPath, startPath, href);
1571
1714
  }
1715
+ /**
1716
+ * Returns a path-relative-scheme-less URL, relative to the
1717
+ * container root.
1718
+ *
1719
+ * @param href The href to resolve
1720
+ * @param [relativeTo] Optional - The href to resolve this href relative to.
1721
+ Use if resolving a relative href from a file other than the package document.
1722
+ */
1723
+ async resolveHref(href, relativeTo, { toRoot } = {}) {
1724
+ const rootfile = await this.getRootfile();
1725
+ const from = relativeTo ? this.resolveInternalHref(rootfile, relativeTo) : rootfile;
1726
+ const path = this.resolveInternalHref(from, href);
1727
+ return path.replace(toRoot ? this.extractPath : dirname(rootfile), "").slice(1);
1728
+ }
1572
1729
  async readFileContents(href, relativeTo, encoding) {
1573
1730
  const rootfile = await this.getRootfile();
1574
- const from = relativeTo ? this.resolveHref(rootfile, relativeTo) : rootfile;
1575
- const path = this.resolveHref(from, href);
1731
+ const from = relativeTo ? this.resolveInternalHref(rootfile, relativeTo) : rootfile;
1732
+ const path = this.resolveInternalHref(from, href);
1576
1733
  const itemEntry = encoding ? await this.getFileData(path, encoding) : await this.getFileData(path);
1577
1734
  return itemEntry;
1578
1735
  }
@@ -1582,7 +1739,7 @@ ${JSON.stringify(element, null, 2)}`
1582
1739
  const manifestItem = manifest[id];
1583
1740
  if (!manifestItem)
1584
1741
  throw new Error(`Could not find item with id "${id}" in manifest`);
1585
- const path = this.resolveHref(rootfile, manifestItem.href);
1742
+ const path = this.resolveInternalHref(rootfile, manifestItem.href);
1586
1743
  const itemEntry = encoding ? await this.getFileData(path, encoding) : await this.getFileData(path);
1587
1744
  return itemEntry;
1588
1745
  }
@@ -1632,7 +1789,7 @@ ${JSON.stringify(element, null, 2)}`
1632
1789
  if (!manifestItem)
1633
1790
  throw new Error(`Could not find item with id "${id}" in manifest`);
1634
1791
  memoize.clear(this.readXhtmlItemContents);
1635
- const href = this.resolveHref(rootfile, manifestItem.href);
1792
+ const href = this.resolveInternalHref(rootfile, manifestItem.href);
1636
1793
  if (encoding === "utf-8") {
1637
1794
  await this.writeEntryContents(href, contents, encoding);
1638
1795
  } else {
@@ -1707,7 +1864,7 @@ ${JSON.stringify(element, null, 2)}`
1707
1864
  });
1708
1865
  this.manifest = null;
1709
1866
  const rootfile = await this.getRootfile();
1710
- const filename = this.resolveHref(rootfile, item.href);
1867
+ const filename = this.resolveInternalHref(rootfile, item.href);
1711
1868
  const data = encoding === "utf-8" || encoding === "xml" ? new TextEncoder().encode(
1712
1869
  encoding === "utf-8" ? contents : await Epub.xmlBuilder.build(
1713
1870
  contents
@@ -1864,6 +2021,105 @@ ${JSON.stringify(element, null, 2)}`
1864
2021
  }
1865
2022
  });
1866
2023
  }
2024
+ /**
2025
+ * Returns the EPUB version declared on the package element.
2026
+ */
2027
+ async getVersion() {
2028
+ var _a;
2029
+ const packageElement = await this.getPackageElement();
2030
+ return ((_a = packageElement[":@"]) == null ? void 0 : _a["@_version"]) ?? "2.0";
2031
+ }
2032
+ /**
2033
+ * Parse the NCX table of contents, if one exists, and return
2034
+ * a tree of TocEntry nodes.
2035
+ *
2036
+ * Useful for both EPUB 2 publications (where the NCX is the
2037
+ * primary navigation) and EPUB 3 publications that retain an
2038
+ * NCX for backwards compatibility.
2039
+ */
2040
+ async getNcxTableOfContents() {
2041
+ var _a;
2042
+ const [manifest, packageElement] = await Promise.all([
2043
+ this.getManifest(),
2044
+ this.getPackageElement()
2045
+ ]);
2046
+ const spine = Epub.findXmlChildByName(
2047
+ "spine",
2048
+ Epub.getXmlChildren(packageElement)
2049
+ );
2050
+ const spineTocId = (_a = spine == null ? void 0 : spine[":@"]) == null ? void 0 : _a["@_toc"];
2051
+ const ncxItem = spineTocId ? manifest[spineTocId] : Object.values(manifest).find(
2052
+ (item) => {
2053
+ var _a2;
2054
+ return ((_a2 = item.mediaType) == null ? void 0 : _a2.toLowerCase()) === "application/x-dtbncx+xml";
2055
+ }
2056
+ );
2057
+ if (!ncxItem) return [];
2058
+ const ncxContent = await this.readItemContents(ncxItem.id, "utf-8");
2059
+ const ncxXml = Epub.xmlParser.parse(ncxContent);
2060
+ const ncxElement = Epub.findXmlChildByName("ncx", ncxXml);
2061
+ if (!ncxElement) return [];
2062
+ const ncxChildren = Epub.getXmlChildren(ncxElement);
2063
+ const navMap = Epub.findXmlChildByName("navMap", ncxChildren) ?? Epub.findXmlChildByName("navmap", ncxChildren);
2064
+ if (!navMap) return [];
2065
+ return this.parseNavPoints(Epub.getXmlChildren(navMap), ncxItem.href);
2066
+ }
2067
+ async parseNavPoints(nodes, ncxHref) {
2068
+ var _a;
2069
+ const entries = [];
2070
+ for (const node of nodes) {
2071
+ if (Epub.isXmlTextNode(node)) continue;
2072
+ const name = Epub.getXmlElementName(node);
2073
+ const isNavPoint = name === "navPoint" || name === "navpoint";
2074
+ if (!isNavPoint) continue;
2075
+ const children = Epub.getXmlChildren(node);
2076
+ const navLabel = Epub.findXmlChildByName("navLabel", children) ?? Epub.findXmlChildByName("navlabel", children);
2077
+ let title = null;
2078
+ if (navLabel) {
2079
+ const textEl = Epub.findXmlChildByName(
2080
+ "text",
2081
+ Epub.getXmlChildren(navLabel)
2082
+ );
2083
+ if (textEl) {
2084
+ title = Epub.getXhtmlTextContent(Epub.getXmlChildren(textEl)).trim() || null;
2085
+ }
2086
+ }
2087
+ const contentEl = Epub.findXmlChildByName("content", children);
2088
+ const src = (_a = contentEl == null ? void 0 : contentEl[":@"]) == null ? void 0 : _a["@_src"];
2089
+ const href = src ? await this.resolveHref(src, ncxHref) : null;
2090
+ const childEntries = await this.parseNavPoints(children, ncxHref);
2091
+ entries.push({
2092
+ title: title ?? `${entries.length}`,
2093
+ ...href && { href },
2094
+ children: childEntries
2095
+ });
2096
+ }
2097
+ return entries;
2098
+ }
2099
+ /**
2100
+ * Retrieve the guide entries from the package document.
2101
+ *
2102
+ * The guide element is deprecated in EPUB 3 in favor of
2103
+ * the landmarks nav, but many publications still include it.
2104
+ */
2105
+ async getGuideEntries() {
2106
+ const packageElement = await this.getPackageElement();
2107
+ const guide = Epub.findXmlChildByName(
2108
+ "guide",
2109
+ Epub.getXmlChildren(packageElement)
2110
+ );
2111
+ if (!guide) return [];
2112
+ return Epub.getXmlChildren(guide).filter(
2113
+ (node) => !Epub.isXmlTextNode(node) && "reference" in node
2114
+ ).map((ref) => {
2115
+ var _a, _b, _c;
2116
+ return {
2117
+ href: ((_a = ref[":@"]) == null ? void 0 : _a["@_href"]) ?? "",
2118
+ title: ((_b = ref[":@"]) == null ? void 0 : _b["@_title"]) ?? "",
2119
+ type: (((_c = ref[":@"]) == null ? void 0 : _c["@_type"]) ?? "").toLowerCase()
2120
+ };
2121
+ }).filter((entry) => entry.href);
2122
+ }
1867
2123
  discardAndClose() {
1868
2124
  this.rootfile = null;
1869
2125
  this.manifest = null;
@@ -1934,10 +2190,75 @@ ${JSON.stringify(element, null, 2)}`
1934
2190
  _promise && await _promise;
1935
2191
  }
1936
2192
  }
2193
+ /**
2194
+ * Upgrade an EPUB 2 publication to EPUB 3 in place, returning a new, valid Epub 3 instance.
2195
+ *
2196
+ * Performs the following transformations:
2197
+ * - upgrades OPF metadata to EPUB 3 conventions
2198
+ * - scans XHTML documents and adds manifest item properties
2199
+ * - parses the NCX into a TOC tree and generates a nav.xhtml
2200
+ * - removes the NCX file and the guide element (configurable)
2201
+ * - fixes common font MIME types
2202
+ * - bumps the package version to 3.0
2203
+ * - goes over each xhtml item and rewrites it using XMLParser to make sure the output is valid XHTML
2204
+ */
2205
+ static async upgrade(path, options = {}) {
2206
+ var _a;
2207
+ const { removeNcx = false, outputPath } = options;
2208
+ if (outputPath) {
2209
+ await mkdir(dirname(outputPath), { recursive: true });
2210
+ await cp(path, outputPath, { force: true });
2211
+ }
2212
+ const epub = await Epub.open(outputPath ?? path);
2213
+ const version = await epub.getVersion();
2214
+ if (version.startsWith("3.")) {
2215
+ return epub;
2216
+ }
2217
+ const tocEntries = await epub.getNcxTableOfContents();
2218
+ let landmarks = [];
2219
+ await epub.withPackage((pkg) => {
2220
+ landmarks = Upgrade.extractGuideLandmarks(pkg);
2221
+ Upgrade.upgradePackageMetadata(pkg);
2222
+ Upgrade.fixFontMimeTypes(pkg);
2223
+ Upgrade.removeGuide(pkg);
2224
+ if (removeNcx) {
2225
+ Upgrade.removeSpineTocRef(pkg);
2226
+ }
2227
+ Upgrade.setPackageVersion(pkg, "3.0");
2228
+ });
2229
+ await Upgrade.collectManifestProperties(epub);
2230
+ if (removeNcx) {
2231
+ await Upgrade.removeNcx(epub);
2232
+ }
2233
+ const navHref = await Upgrade.chooseNavHref(epub);
2234
+ const navContent = await Upgrade.buildNavDocument(
2235
+ epub,
2236
+ tocEntries,
2237
+ landmarks
2238
+ );
2239
+ await epub.addManifestItem(
2240
+ {
2241
+ id: "nav",
2242
+ href: navHref,
2243
+ mediaType: "application/xhtml+xml",
2244
+ properties: ["nav"]
2245
+ },
2246
+ navContent,
2247
+ "utf-8"
2248
+ );
2249
+ const manifest = await epub.getManifest();
2250
+ for (const item of Object.values(manifest)) {
2251
+ if (((_a = item.mediaType) == null ? void 0 : _a.toLowerCase()) !== "application/xhtml+xml") continue;
2252
+ const contents = await epub.readXhtmlItemContents(item.id);
2253
+ await epub.writeXhtmlItemContents(item.id, contents);
2254
+ }
2255
+ return epub;
2256
+ }
1937
2257
  [Symbol.dispose]() {
1938
2258
  this.discardAndClose();
1939
2259
  }
1940
2260
  }
1941
2261
  export {
1942
- Epub
2262
+ Epub,
2263
+ EpubVersionError
1943
2264
  };