@storyteller-platform/epub 0.4.10 → 0.5.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.
@@ -0,0 +1,50 @@
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
+ };
46
+
47
+ export {
48
+ __using,
49
+ __callDispose
50
+ };
package/dist/index.cjs CHANGED
@@ -73,7 +73,8 @@ var __callDispose = (stack, error, hasError) => {
73
73
  };
74
74
  var index_exports = {};
75
75
  __export(index_exports, {
76
- Epub: () => Epub
76
+ Epub: () => Epub,
77
+ EpubVersionError: () => EpubVersionError
77
78
  });
78
79
  module.exports = __toCommonJS(index_exports);
79
80
  var import_node_crypto = require("node:crypto");
@@ -89,6 +90,7 @@ var import_nanoid = require("nanoid");
89
90
  var import_yauzl_promise = require("yauzl-promise");
90
91
  var import_yazl = require("yazl");
91
92
  var import_path = require("@storyteller-platform/path");
93
+ var Upgrade = __toESM(require("./upgrade.ts"), 1);
92
94
  const MP3_FILE_EXTENSIONS = [".mp3"];
93
95
  const MPEG4_FILE_EXTENSIONS = [".mp4", ".m4a", ".m4b"];
94
96
  const AAC_FILE_EXTENSIONS = [".aac"];
@@ -114,6 +116,8 @@ const AUDIO_FILE_EXTENSIONS = [
114
116
  function isAudioFile(filenameOrExt) {
115
117
  return AUDIO_FILE_EXTENSIONS.some((ext) => filenameOrExt.endsWith(ext));
116
118
  }
119
+ class EpubVersionError extends Error {
120
+ }
117
121
  class Epub {
118
122
  constructor(extractPath, inputPath) {
119
123
  this.extractPath = extractPath;
@@ -278,9 +282,29 @@ ${JSON.stringify(element, null, 2)}`
278
282
  * the provided name and optional filter.
279
283
  */
280
284
  static findXmlChildByName(name, xml, filter) {
281
- const element = xml.find((e) => name in e && (filter ? filter(e) : true));
285
+ const element = xml.find(
286
+ (e) => name in e && (filter ? filter(e) : true)
287
+ );
282
288
  return element;
283
289
  }
290
+ /**
291
+ * Given an XML structure, find the first descendant matching
292
+ * the provided name and optional filter.
293
+ *
294
+ * Will perform a breadth first search for the element, returning
295
+ * the highest element in the tree matching the name and filter.
296
+ */
297
+ static findXmlDescendantByName(name, xml, filter) {
298
+ const found = Epub.findXmlChildByName(name, xml, filter);
299
+ if (found) return found;
300
+ for (const node of xml) {
301
+ if (Epub.isXmlTextNode(node)) continue;
302
+ const children = Epub.getXmlChildren(node);
303
+ const found2 = this.findXmlDescendantByName(name, children, filter);
304
+ if (found2) return found2;
305
+ }
306
+ return void 0;
307
+ }
284
308
  /**
285
309
  * Given an XMLNode, determine whether it represents
286
310
  * a text node or an XML element.
@@ -371,9 +395,21 @@ ${JSON.stringify(element, null, 2)}`
371
395
  * path to an EPUB file on disk, or a Uint8Array representing
372
396
  * the data of the EPUB publication.
373
397
  */
374
- // eslint-disable-next-line @typescript-eslint/require-await
375
398
  static async from(pathOrData) {
376
- var _a, _b;
399
+ const epub = await this.open(pathOrData);
400
+ const version = await epub.getVersion();
401
+ if (!version.startsWith("3.")) {
402
+ epub.discardAndClose();
403
+ throw new EpubVersionError(
404
+ "This is not a valid EPUB 3 publication. This library only supports EPUB 3, not EPUB 2. Use Epub.upgrade(path) to convert."
405
+ );
406
+ }
407
+ return epub;
408
+ }
409
+ /**
410
+ * Open an EPUB publication and return an Epub instance.
411
+ */
412
+ static async open(pathOrData) {
377
413
  const extractPath = (0, import_path.join)(
378
414
  (0, import_node_os.tmpdir)(),
379
415
  `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}.epub`
@@ -387,7 +423,7 @@ ${JSON.stringify(element, null, 2)}`
387
423
  await zipfile.close();
388
424
  });
389
425
  for await (const entry of zipfile) {
390
- if (entry.filename.endsWith("/")) {
426
+ if (entry.filename.endsWith(import_path.sep)) {
391
427
  } else {
392
428
  const writePath = (0, import_path.join)(extractPath, entry.filename);
393
429
  const readStream = await entry.openReadStream();
@@ -416,13 +452,7 @@ ${JSON.stringify(element, null, 2)}`
416
452
  epub.discardAndClose();
417
453
  console.error(e);
418
454
  throw new Error(
419
- "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."
420
- );
421
- }
422
- const packageEl = await epub.getPackageElement();
423
- if (!((_b = (_a = packageEl[":@"]) == null ? void 0 : _a["@_version"]) == null ? void 0 : _b.startsWith("3."))) {
424
- throw new Error(
425
- "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."
455
+ "This is not a valid EPUB publication. Could not read the package document."
426
456
  );
427
457
  }
428
458
  return epub;
@@ -442,7 +472,7 @@ ${JSON.stringify(element, null, 2)}`
442
472
  }
443
473
  async removeEntry(href) {
444
474
  const rootfile = await this.getRootfile();
445
- const filename = this.resolveHref(rootfile, href);
475
+ const filename = this.resolveInternalHref(rootfile, href);
446
476
  await (0, import_promises.rm)(filename);
447
477
  }
448
478
  async getFileData(path, encoding) {
@@ -815,6 +845,57 @@ ${JSON.stringify(element, null, 2)}`
815
845
  value: date.toISOString()
816
846
  });
817
847
  }
848
+ /**
849
+ * Retrieve the modified date from the dcterms:modified metadata
850
+ * in the EPUB metadata as a Date object.
851
+ *
852
+ * If there is no meta element with dcterms:modified, returns null.
853
+ *
854
+ * @link https://www.w3.org/TR/epub-33/#sec-metadata-last-modified
855
+ */
856
+ async getModifiedDate() {
857
+ const metadata = await this.getMetadata();
858
+ const entry = metadata.find(
859
+ ({ properties }) => properties["property"] === "dcterms:modified"
860
+ );
861
+ if (!(entry == null ? void 0 : entry.value)) return null;
862
+ return new Date(entry.value);
863
+ }
864
+ /**
865
+ * Retrieve the layout from the rendition:layout meta element
866
+ * in the EPUB metadata.
867
+ *
868
+ * If there is no meta element, returns 'reflowable'.
869
+ *
870
+ * @link https://www.w3.org/TR/epub-33/#layout
871
+ */
872
+ async getLayout() {
873
+ const metadata = await this.getMetadata();
874
+ const entry = metadata.find(
875
+ ({ properties }) => properties["property"] === "rendition:layout"
876
+ );
877
+ if ((entry == null ? void 0 : entry.value) !== "reflowable" && (entry == null ? void 0 : entry.value) !== "pre-paginated") {
878
+ return "reflowable";
879
+ }
880
+ return entry.value;
881
+ }
882
+ /**
883
+ * Retrieve the base direction from the package element.
884
+ *
885
+ * If there is no `dir` attribute on the package element,
886
+ * returns 'auto'.
887
+ *
888
+ * @link https://www.w3.org/TR/epub-33/#attrdef-dir
889
+ */
890
+ async getBaseDirection() {
891
+ var _a;
892
+ const packageEl = await this.getPackageElement();
893
+ const dir = (_a = packageEl[":@"]) == null ? void 0 : _a["@_dir"];
894
+ if (dir !== "ltr" && dir !== "rtl" && dir !== "auto") {
895
+ return "auto";
896
+ }
897
+ return dir;
898
+ }
818
899
  /**
819
900
  * Set the dc:type metadata element.
820
901
  *
@@ -1595,17 +1676,139 @@ ${JSON.stringify(element, null, 2)}`
1595
1676
  });
1596
1677
  this.spine = null;
1597
1678
  }
1679
+ async getNavigationChildren(ol, navHref, { resolveToRoot } = {}) {
1680
+ var _a;
1681
+ const children = [];
1682
+ const childrenElements = Epub.getXmlChildren(ol).filter(
1683
+ (node) => !Epub.isXmlTextNode(node) && Epub.getXmlElementName(node) === "li"
1684
+ );
1685
+ for (const childEl of childrenElements) {
1686
+ const [firstChild, secondChild] = Epub.getXmlChildren(childEl).filter(
1687
+ (node) => !Epub.isXmlTextNode(node) && ["a", "span", "ol"].includes(Epub.getXmlElementName(node))
1688
+ );
1689
+ if (!firstChild) continue;
1690
+ if (!["a", "span"].includes(Epub.getXmlElementName(firstChild))) {
1691
+ continue;
1692
+ }
1693
+ if (Epub.getXmlElementName(firstChild) === "span" && (!secondChild || Epub.getXmlElementName(secondChild) !== "ol")) {
1694
+ continue;
1695
+ }
1696
+ children.push({
1697
+ title: Epub.getXhtmlTextContent(Epub.getXmlChildren(firstChild)),
1698
+ ...Epub.getXmlElementName(firstChild) === "a" && ((_a = firstChild[":@"]) == null ? void 0 : _a["@_href"]) && {
1699
+ href: await this.resolveHref(
1700
+ firstChild[":@"]["@_href"],
1701
+ void 0,
1702
+ { toRoot: resolveToRoot }
1703
+ )
1704
+ },
1705
+ ...secondChild && Epub.getXmlElementName(secondChild) === "ol" && {
1706
+ children: await this.getNavigationChildren(secondChild, navHref, {
1707
+ resolveToRoot
1708
+ })
1709
+ }
1710
+ });
1711
+ }
1712
+ return children;
1713
+ }
1714
+ async getNavigation(role, { resolveToRoot } = {}) {
1715
+ const manifest = await this.getManifest();
1716
+ const navItem = Object.values(manifest).find(
1717
+ (item) => {
1718
+ var _a;
1719
+ return (_a = item.properties) == null ? void 0 : _a.includes("nav");
1720
+ }
1721
+ );
1722
+ if (!navItem) return null;
1723
+ const navContents = await this.readXhtmlItemContents(navItem.id);
1724
+ const navEl = Epub.findXmlDescendantByName(
1725
+ "nav",
1726
+ navContents,
1727
+ (node) => Epub.getXmlAttributes(node)["epub:type"] === role
1728
+ );
1729
+ if (!navEl) return null;
1730
+ const [firstChild, secondChild] = Epub.getXmlChildren(navEl).filter(
1731
+ (node) => !!(!Epub.isXmlTextNode(node) && Epub.getXmlElementName(node).match(/(?:h[1-6]|ol)/))
1732
+ );
1733
+ if (!firstChild) return null;
1734
+ const title = Epub.getXmlElementName(firstChild).match(/h[1-6]/) ? Epub.getXhtmlTextContent(Epub.getXmlChildren(firstChild)) : null;
1735
+ const list = Epub.getXmlElementName(firstChild) === "ol" ? firstChild : secondChild && Epub.getXmlElementName(secondChild) === "ol" ? secondChild : null;
1736
+ if (!list) return null;
1737
+ const children = await this.getNavigationChildren(list, navItem.href, {
1738
+ resolveToRoot
1739
+ });
1740
+ return {
1741
+ ...title && { title },
1742
+ children
1743
+ };
1744
+ }
1745
+ /**
1746
+ * Returns the structured table of contents navigation document
1747
+ * as a Navigation object.
1748
+ *
1749
+ * @link https://www.w3.org/TR/epub-33/#sec-nav-toc
1750
+ */
1751
+ async getTableOfContents({
1752
+ resolveToRoot
1753
+ } = {}) {
1754
+ const navigationToc = await this.getNavigation("toc", { resolveToRoot });
1755
+ if (navigationToc) return navigationToc;
1756
+ const ncxToc = await this.getNcxTableOfContents();
1757
+ return {
1758
+ children: ncxToc
1759
+ };
1760
+ }
1761
+ /**
1762
+ * Returns the structured landmarks navigation document
1763
+ * as a Navigation object
1764
+ *
1765
+ * @link https://www.w3.org/TR/epub-33/#sec-nav-landmarks
1766
+ */
1767
+ async getLandmarks({
1768
+ resolveToRoot
1769
+ } = {}) {
1770
+ return this.getNavigation("landmarks", { resolveToRoot });
1771
+ }
1772
+ /**
1773
+ * Returns the structured page list navigation document
1774
+ * as a Navigation object
1775
+ *
1776
+ * @link https://www.w3.org/TR/epub-33/#sec-nav-landmarks
1777
+ */
1778
+ async getPageList({
1779
+ resolveToRoot
1780
+ } = {}) {
1781
+ return this.getNavigation("page-list", { resolveToRoot });
1782
+ }
1598
1783
  /**
1599
1784
  * Returns a Zip Entry path for an HREF
1600
1785
  */
1601
- resolveHref(from, href) {
1786
+ resolveInternalHref(from, href) {
1602
1787
  const startPath = (0, import_path.dirname)(from);
1603
- return (0, import_path.resolve)(this.extractPath, startPath, href);
1788
+ return (0, import_path.resolve)(
1789
+ this.extractPath,
1790
+ (0, import_path.hrefToPlatformPath)(startPath),
1791
+ (0, import_path.hrefToPlatformPath)(href)
1792
+ );
1793
+ }
1794
+ /**
1795
+ * Returns a path-relative-scheme-less URL, relative to the
1796
+ * container root.
1797
+ *
1798
+ * @param href The href to resolve
1799
+ * @param [relativeTo] Optional - The href to resolve this href relative to.
1800
+ Use if resolving a relative href from a file other than the package document.
1801
+ */
1802
+ async resolveHref(href, relativeTo, { toRoot } = {}) {
1803
+ const rootfile = await this.getRootfile();
1804
+ const from = relativeTo ? this.resolveInternalHref(rootfile, relativeTo) : rootfile;
1805
+ const path = this.resolveInternalHref(from, href);
1806
+ return path.replace(toRoot ? this.extractPath : (0, import_path.dirname)(rootfile), "").slice(1);
1604
1807
  }
1605
1808
  async readFileContents(href, relativeTo, encoding) {
1606
1809
  const rootfile = await this.getRootfile();
1607
- const from = relativeTo ? this.resolveHref(rootfile, relativeTo) : rootfile;
1608
- const path = this.resolveHref(from, href);
1810
+ const from = relativeTo ? this.resolveInternalHref(rootfile, relativeTo) : rootfile;
1811
+ const path = this.resolveInternalHref(from, href);
1609
1812
  const itemEntry = encoding ? await this.getFileData(path, encoding) : await this.getFileData(path);
1610
1813
  return itemEntry;
1611
1814
  }
@@ -1615,7 +1818,7 @@ ${JSON.stringify(element, null, 2)}`
1615
1818
  const manifestItem = manifest[id];
1616
1819
  if (!manifestItem)
1617
1820
  throw new Error(`Could not find item with id "${id}" in manifest`);
1618
- const path = this.resolveHref(rootfile, manifestItem.href);
1821
+ const path = this.resolveInternalHref(rootfile, manifestItem.href);
1619
1822
  const itemEntry = encoding ? await this.getFileData(path, encoding) : await this.getFileData(path);
1620
1823
  return itemEntry;
1621
1824
  }
@@ -1665,7 +1868,7 @@ ${JSON.stringify(element, null, 2)}`
1665
1868
  if (!manifestItem)
1666
1869
  throw new Error(`Could not find item with id "${id}" in manifest`);
1667
1870
  import_mem.default.clear(this.readXhtmlItemContents);
1668
- const href = this.resolveHref(rootfile, manifestItem.href);
1871
+ const href = this.resolveInternalHref(rootfile, manifestItem.href);
1669
1872
  if (encoding === "utf-8") {
1670
1873
  await this.writeEntryContents(href, contents, encoding);
1671
1874
  } else {
@@ -1740,7 +1943,7 @@ ${JSON.stringify(element, null, 2)}`
1740
1943
  });
1741
1944
  this.manifest = null;
1742
1945
  const rootfile = await this.getRootfile();
1743
- const filename = this.resolveHref(rootfile, item.href);
1946
+ const filename = this.resolveInternalHref(rootfile, item.href);
1744
1947
  const data = encoding === "utf-8" || encoding === "xml" ? new TextEncoder().encode(
1745
1948
  encoding === "utf-8" ? contents : await Epub.xmlBuilder.build(
1746
1949
  contents
@@ -1897,6 +2100,105 @@ ${JSON.stringify(element, null, 2)}`
1897
2100
  }
1898
2101
  });
1899
2102
  }
2103
+ /**
2104
+ * Returns the EPUB version declared on the package element.
2105
+ */
2106
+ async getVersion() {
2107
+ var _a;
2108
+ const packageElement = await this.getPackageElement();
2109
+ return ((_a = packageElement[":@"]) == null ? void 0 : _a["@_version"]) ?? "2.0";
2110
+ }
2111
+ /**
2112
+ * Parse the NCX table of contents, if one exists, and return
2113
+ * a tree of TocEntry nodes.
2114
+ *
2115
+ * Useful for both EPUB 2 publications (where the NCX is the
2116
+ * primary navigation) and EPUB 3 publications that retain an
2117
+ * NCX for backwards compatibility.
2118
+ */
2119
+ async getNcxTableOfContents() {
2120
+ var _a;
2121
+ const [manifest, packageElement] = await Promise.all([
2122
+ this.getManifest(),
2123
+ this.getPackageElement()
2124
+ ]);
2125
+ const spine = Epub.findXmlChildByName(
2126
+ "spine",
2127
+ Epub.getXmlChildren(packageElement)
2128
+ );
2129
+ const spineTocId = (_a = spine == null ? void 0 : spine[":@"]) == null ? void 0 : _a["@_toc"];
2130
+ const ncxItem = spineTocId ? manifest[spineTocId] : Object.values(manifest).find(
2131
+ (item) => {
2132
+ var _a2;
2133
+ return ((_a2 = item.mediaType) == null ? void 0 : _a2.toLowerCase()) === "application/x-dtbncx+xml";
2134
+ }
2135
+ );
2136
+ if (!ncxItem) return [];
2137
+ const ncxContent = await this.readItemContents(ncxItem.id, "utf-8");
2138
+ const ncxXml = Epub.xmlParser.parse(ncxContent);
2139
+ const ncxElement = Epub.findXmlChildByName("ncx", ncxXml);
2140
+ if (!ncxElement) return [];
2141
+ const ncxChildren = Epub.getXmlChildren(ncxElement);
2142
+ const navMap = Epub.findXmlChildByName("navMap", ncxChildren) ?? Epub.findXmlChildByName("navmap", ncxChildren);
2143
+ if (!navMap) return [];
2144
+ return this.parseNavPoints(Epub.getXmlChildren(navMap), ncxItem.href);
2145
+ }
2146
+ async parseNavPoints(nodes, ncxHref) {
2147
+ var _a;
2148
+ const entries = [];
2149
+ for (const node of nodes) {
2150
+ if (Epub.isXmlTextNode(node)) continue;
2151
+ const name = Epub.getXmlElementName(node);
2152
+ const isNavPoint = name === "navPoint" || name === "navpoint";
2153
+ if (!isNavPoint) continue;
2154
+ const children = Epub.getXmlChildren(node);
2155
+ const navLabel = Epub.findXmlChildByName("navLabel", children) ?? Epub.findXmlChildByName("navlabel", children);
2156
+ let title = null;
2157
+ if (navLabel) {
2158
+ const textEl = Epub.findXmlChildByName(
2159
+ "text",
2160
+ Epub.getXmlChildren(navLabel)
2161
+ );
2162
+ if (textEl) {
2163
+ title = Epub.getXhtmlTextContent(Epub.getXmlChildren(textEl)).trim() || null;
2164
+ }
2165
+ }
2166
+ const contentEl = Epub.findXmlChildByName("content", children);
2167
+ const src = (_a = contentEl == null ? void 0 : contentEl[":@"]) == null ? void 0 : _a["@_src"];
2168
+ const href = src ? await this.resolveHref(src, ncxHref) : null;
2169
+ const childEntries = await this.parseNavPoints(children, ncxHref);
2170
+ entries.push({
2171
+ title: title ?? `${entries.length}`,
2172
+ ...href && { href },
2173
+ children: childEntries
2174
+ });
2175
+ }
2176
+ return entries;
2177
+ }
2178
+ /**
2179
+ * Retrieve the guide entries from the package document.
2180
+ *
2181
+ * The guide element is deprecated in EPUB 3 in favor of
2182
+ * the landmarks nav, but many publications still include it.
2183
+ */
2184
+ async getGuideEntries() {
2185
+ const packageElement = await this.getPackageElement();
2186
+ const guide = Epub.findXmlChildByName(
2187
+ "guide",
2188
+ Epub.getXmlChildren(packageElement)
2189
+ );
2190
+ if (!guide) return [];
2191
+ return Epub.getXmlChildren(guide).filter(
2192
+ (node) => !Epub.isXmlTextNode(node) && "reference" in node
2193
+ ).map((ref) => {
2194
+ var _a, _b, _c;
2195
+ return {
2196
+ href: ((_a = ref[":@"]) == null ? void 0 : _a["@_href"]) ?? "",
2197
+ title: ((_b = ref[":@"]) == null ? void 0 : _b["@_title"]) ?? "",
2198
+ type: (((_c = ref[":@"]) == null ? void 0 : _c["@_type"]) ?? "").toLowerCase()
2199
+ };
2200
+ }).filter((entry) => entry.href);
2201
+ }
1900
2202
  discardAndClose() {
1901
2203
  this.rootfile = null;
1902
2204
  this.manifest = null;
@@ -1967,11 +2269,76 @@ ${JSON.stringify(element, null, 2)}`
1967
2269
  _promise && await _promise;
1968
2270
  }
1969
2271
  }
2272
+ /**
2273
+ * Upgrade an EPUB 2 publication to EPUB 3 in place, returning a new, valid Epub 3 instance.
2274
+ *
2275
+ * Performs the following transformations:
2276
+ * - upgrades OPF metadata to EPUB 3 conventions
2277
+ * - scans XHTML documents and adds manifest item properties
2278
+ * - parses the NCX into a TOC tree and generates a nav.xhtml
2279
+ * - removes the NCX file and the guide element (configurable)
2280
+ * - fixes common font MIME types
2281
+ * - bumps the package version to 3.0
2282
+ * - goes over each xhtml item and rewrites it using XMLParser to make sure the output is valid XHTML
2283
+ */
2284
+ static async upgrade(path, options = {}) {
2285
+ var _a;
2286
+ const { removeNcx = false, outputPath } = options;
2287
+ if (outputPath) {
2288
+ await (0, import_promises.mkdir)((0, import_path.dirname)(outputPath), { recursive: true });
2289
+ await (0, import_promises.cp)(path, outputPath, { force: true });
2290
+ }
2291
+ const epub = await Epub.open(outputPath ?? path);
2292
+ const version = await epub.getVersion();
2293
+ if (version.startsWith("3.")) {
2294
+ return epub;
2295
+ }
2296
+ const tocEntries = await epub.getNcxTableOfContents();
2297
+ let landmarks = [];
2298
+ await epub.withPackage((pkg) => {
2299
+ landmarks = Upgrade.extractGuideLandmarks(pkg);
2300
+ Upgrade.upgradePackageMetadata(pkg);
2301
+ Upgrade.fixFontMimeTypes(pkg);
2302
+ Upgrade.removeGuide(pkg);
2303
+ if (removeNcx) {
2304
+ Upgrade.removeSpineTocRef(pkg);
2305
+ }
2306
+ Upgrade.setPackageVersion(pkg, "3.0");
2307
+ });
2308
+ await Upgrade.collectManifestProperties(epub);
2309
+ if (removeNcx) {
2310
+ await Upgrade.removeNcx(epub);
2311
+ }
2312
+ const navHref = await Upgrade.chooseNavHref(epub);
2313
+ const navContent = await Upgrade.buildNavDocument(
2314
+ epub,
2315
+ tocEntries,
2316
+ landmarks
2317
+ );
2318
+ await epub.addManifestItem(
2319
+ {
2320
+ id: "nav",
2321
+ href: navHref,
2322
+ mediaType: "application/xhtml+xml",
2323
+ properties: ["nav"]
2324
+ },
2325
+ navContent,
2326
+ "utf-8"
2327
+ );
2328
+ const manifest = await epub.getManifest();
2329
+ for (const item of Object.values(manifest)) {
2330
+ if (((_a = item.mediaType) == null ? void 0 : _a.toLowerCase()) !== "application/xhtml+xml") continue;
2331
+ const contents = await epub.readXhtmlItemContents(item.id);
2332
+ await epub.writeXhtmlItemContents(item.id, contents);
2333
+ }
2334
+ return epub;
2335
+ }
1970
2336
  [Symbol.dispose]() {
1971
2337
  this.discardAndClose();
1972
2338
  }
1973
2339
  }
1974
2340
  // Annotate the CommonJS export names for ESM import in node:
1975
2341
  0 && (module.exports = {
1976
- Epub
2342
+ Epub,
2343
+ EpubVersionError
1977
2344
  });