@storyteller-platform/epub 0.6.3 → 0.7.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/README.md +529 -1075
- package/dist/adapters/interface.d.cts +25 -19
- package/dist/adapters/interface.d.ts +25 -19
- package/dist/adapters/memory.cjs +19 -29
- package/dist/adapters/memory.d.cts +4 -5
- package/dist/adapters/memory.d.ts +4 -5
- package/dist/adapters/memory.js +19 -32
- package/dist/adapters/tmpfs.cjs +50 -45
- package/dist/adapters/tmpfs.d.cts +9 -7
- package/dist/adapters/tmpfs.d.ts +9 -7
- package/dist/adapters/tmpfs.js +50 -45
- package/dist/epubcheck.cjs +2 -2
- package/dist/epubcheck.js +2 -2
- package/dist/index.cjs +585 -94
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +586 -100
- package/dist/upgrade.cjs +12 -44
- package/dist/upgrade.d.cts +175 -11
- package/dist/upgrade.d.ts +175 -11
- package/dist/upgrade.js +12 -44
- package/dist/zip.cjs +142 -0
- package/dist/zip.d.cts +5 -0
- package/dist/zip.d.ts +5 -0
- package/dist/zip.js +124 -0
- package/package.json +4 -5
package/dist/index.cjs
CHANGED
|
@@ -40,8 +40,8 @@ var import_promises = require("node:fs/promises");
|
|
|
40
40
|
var import_async_mutex = require("async-mutex");
|
|
41
41
|
var import_fast_xml_parser = require("fast-xml-parser");
|
|
42
42
|
var import_mem = __toESM(require("mem"), 1);
|
|
43
|
-
var import_mime_types = require("mime-types");
|
|
44
43
|
var import_nanoid = require("nanoid");
|
|
44
|
+
var import_media_types = require("@storyteller-platform/media-types");
|
|
45
45
|
var import_path = require("@storyteller-platform/path");
|
|
46
46
|
var import_tmpfs = require("./adapters/tmpfs.cjs");
|
|
47
47
|
var Upgrade = __toESM(require("./upgrade.ts"), 1);
|
|
@@ -51,6 +51,61 @@ class EpubVersionError extends Error {
|
|
|
51
51
|
}
|
|
52
52
|
class EpubReadOnlyError extends Error {
|
|
53
53
|
}
|
|
54
|
+
const OPF_NAMESPACE = "http://www.idpf.org/2007/opf";
|
|
55
|
+
const ENTRY_ORIGIN = "epub:///";
|
|
56
|
+
function entryToUrlPath(entry) {
|
|
57
|
+
return entry.split("/").map(encodeURIComponent).join("/");
|
|
58
|
+
}
|
|
59
|
+
function resolveEntryUrl(from, href) {
|
|
60
|
+
const base = new URL(entryToUrlPath(from), ENTRY_ORIGIN);
|
|
61
|
+
if (!URL.canParse(href, base.toString())) return null;
|
|
62
|
+
const url = new URL(href, base);
|
|
63
|
+
return url.href.startsWith(ENTRY_ORIGIN) ? url : null;
|
|
64
|
+
}
|
|
65
|
+
function renameOpfAttributes(attrs, prefix) {
|
|
66
|
+
const attrPrefix = `@_${prefix}:`;
|
|
67
|
+
const renamed = {};
|
|
68
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
69
|
+
if (key === `@_xmlns:${prefix}`) continue;
|
|
70
|
+
if (key.startsWith(attrPrefix)) {
|
|
71
|
+
renamed[`@_opf:${key.slice(attrPrefix.length)}`] = value;
|
|
72
|
+
} else {
|
|
73
|
+
renamed[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return renamed;
|
|
77
|
+
}
|
|
78
|
+
function stripOpfPrefix(node, prefix) {
|
|
79
|
+
if (Epub.isXmlTextNode(node)) return node;
|
|
80
|
+
const name = Epub.getXmlElementName(node);
|
|
81
|
+
const bareName = name.startsWith(`${prefix}:`) ? name.slice(prefix.length + 1) : name;
|
|
82
|
+
const element = {
|
|
83
|
+
[bareName]: Epub.getXmlChildren(node).map(
|
|
84
|
+
(child) => stripOpfPrefix(child, prefix)
|
|
85
|
+
)
|
|
86
|
+
};
|
|
87
|
+
if (node[":@"]) {
|
|
88
|
+
element[":@"] = renameOpfAttributes(node[":@"], prefix);
|
|
89
|
+
}
|
|
90
|
+
return element;
|
|
91
|
+
}
|
|
92
|
+
function normalizeOpfNamespacePrefixes(doc) {
|
|
93
|
+
var _a;
|
|
94
|
+
const root = doc.find(
|
|
95
|
+
(node) => !Epub.isXmlTextNode(node) && Epub.getXmlElementName(node).endsWith(":package")
|
|
96
|
+
);
|
|
97
|
+
if (!root) return doc;
|
|
98
|
+
const prefix = Epub.getXmlElementName(root).slice(0, -":package".length);
|
|
99
|
+
if (((_a = root[":@"]) == null ? void 0 : _a[`@_xmlns:${prefix}`]) !== OPF_NAMESPACE) return doc;
|
|
100
|
+
const stripped = stripOpfPrefix(root, prefix);
|
|
101
|
+
stripped[":@"] = {
|
|
102
|
+
...stripped[":@"],
|
|
103
|
+
"@_xmlns": OPF_NAMESPACE,
|
|
104
|
+
"@_xmlns:opf": OPF_NAMESPACE
|
|
105
|
+
};
|
|
106
|
+
doc[doc.indexOf(root)] = stripped;
|
|
107
|
+
return doc;
|
|
108
|
+
}
|
|
54
109
|
class Epub {
|
|
55
110
|
/**
|
|
56
111
|
* Prefer the static factories ({@link Epub.using}, {@link Epub.from},
|
|
@@ -331,14 +386,7 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
331
386
|
);
|
|
332
387
|
}
|
|
333
388
|
const rootfile = await this.getRootfile();
|
|
334
|
-
|
|
335
|
-
await this.adapter.remove(filename);
|
|
336
|
-
}
|
|
337
|
-
async getFileData(path, encoding) {
|
|
338
|
-
if (encoding) {
|
|
339
|
-
return this.adapter.read(path, encoding);
|
|
340
|
-
}
|
|
341
|
-
return this.adapter.read(path);
|
|
389
|
+
await this.adapter.remove(await this.resolveEntry(rootfile, href));
|
|
342
390
|
}
|
|
343
391
|
/**
|
|
344
392
|
* Length of the underlying archive entry for a manifest item, in bytes
|
|
@@ -351,14 +399,15 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
351
399
|
const manifestItem = manifest[id];
|
|
352
400
|
if (!manifestItem)
|
|
353
401
|
throw new Error(`Could not find item with id "${id}" in manifest`);
|
|
354
|
-
|
|
355
|
-
|
|
402
|
+
return this.adapter.archiveLength(
|
|
403
|
+
await this.resolveEntry(rootfile, manifestItem.href)
|
|
404
|
+
);
|
|
356
405
|
}
|
|
357
406
|
async getRootfile() {
|
|
358
407
|
var _a;
|
|
359
408
|
if (this.rootfile !== null) return this.rootfile;
|
|
360
|
-
const containerString = await this.
|
|
361
|
-
|
|
409
|
+
const containerString = await this.adapter.read(
|
|
410
|
+
"META-INF/container.xml",
|
|
362
411
|
"utf-8"
|
|
363
412
|
);
|
|
364
413
|
if (!containerString)
|
|
@@ -382,7 +431,7 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
382
431
|
Epub.getXmlChildren(rootfiles),
|
|
383
432
|
(node) => {
|
|
384
433
|
var _a2;
|
|
385
|
-
return !Epub.isXmlTextNode(node) && ((_a2 = node[":@"]) == null ? void 0 : _a2["@_media-type"]) ===
|
|
434
|
+
return !Epub.isXmlTextNode(node) && ((_a2 = node[":@"]) == null ? void 0 : _a2["@_media-type"]) === import_media_types.MediaType.OPF.mime;
|
|
386
435
|
}
|
|
387
436
|
);
|
|
388
437
|
const fullPath = (_a = rootfile == null ? void 0 : rootfile[":@"]) == null ? void 0 : _a["@_full-path"];
|
|
@@ -390,12 +439,12 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
390
439
|
throw new Error(
|
|
391
440
|
"Failed to parse EPUB container.xml: Found no rootfile element"
|
|
392
441
|
);
|
|
393
|
-
this.rootfile =
|
|
442
|
+
this.rootfile = await this.resolveEntry("", fullPath);
|
|
394
443
|
return this.rootfile;
|
|
395
444
|
}
|
|
396
445
|
async getPackageDocument() {
|
|
397
446
|
const rootfile = await this.getRootfile();
|
|
398
|
-
const packageDocumentString = await this.
|
|
447
|
+
const packageDocumentString = await this.adapter.read(rootfile, "utf-8");
|
|
399
448
|
if (!packageDocumentString)
|
|
400
449
|
throw new Error(
|
|
401
450
|
`Failed to parse EPUB: could not find package document at ${rootfile}`
|
|
@@ -403,7 +452,7 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
403
452
|
const packageDocument = Epub.xmlParser.parse(
|
|
404
453
|
packageDocumentString
|
|
405
454
|
);
|
|
406
|
-
return packageDocument;
|
|
455
|
+
return normalizeOpfNamespacePrefixes(packageDocument);
|
|
407
456
|
}
|
|
408
457
|
async getPackageElement() {
|
|
409
458
|
const packageDocument = await this.getPackageDocument();
|
|
@@ -582,12 +631,15 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
582
631
|
return metadata;
|
|
583
632
|
}
|
|
584
633
|
/**
|
|
585
|
-
* Retrieve the identifier from the dc:identifier element
|
|
634
|
+
* Retrieve the first identifier from the dc:identifier element
|
|
586
635
|
* in the EPUB metadata.
|
|
587
636
|
*
|
|
588
637
|
* If there is no dc:identifier element, returns null.
|
|
589
638
|
*
|
|
590
639
|
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
|
|
640
|
+
*
|
|
641
|
+
* @deprecated Use {@link getUniqueIdentifier} instead to get the unique identifier,
|
|
642
|
+
* or {@link getIdentifiers} to get all identifiers.
|
|
591
643
|
*/
|
|
592
644
|
async getIdentifier() {
|
|
593
645
|
const metadata = await this.getMetadata();
|
|
@@ -601,6 +653,8 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
601
653
|
* Otherwise creates a new element
|
|
602
654
|
*
|
|
603
655
|
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
|
|
656
|
+
*
|
|
657
|
+
* @deprecated Use {@link setUniqueIdentifier} instead.
|
|
604
658
|
*/
|
|
605
659
|
async setIdentifier(identifier) {
|
|
606
660
|
await this.replaceMetadata(({ type }) => type === "dc:identifier", {
|
|
@@ -609,6 +663,395 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
609
663
|
value: identifier
|
|
610
664
|
});
|
|
611
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Retrieve the identifier with the unique identifier id
|
|
668
|
+
* in the EPUB metadata.
|
|
669
|
+
*
|
|
670
|
+
* If there is no unique identifier id, returns the first dc:identifier element.
|
|
671
|
+
*
|
|
672
|
+
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
|
|
673
|
+
*/
|
|
674
|
+
async getUniqueIdentifier() {
|
|
675
|
+
const metadata = await this.getMetadata();
|
|
676
|
+
const uniqueId = await this.getUniqueIdentifierId();
|
|
677
|
+
if (!uniqueId) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
const entry = metadata.find(
|
|
681
|
+
({ type, id }) => type === "dc:identifier" && id === uniqueId
|
|
682
|
+
);
|
|
683
|
+
return (entry == null ? void 0 : entry.value) ?? null;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Set the unique identifier id for the EPUB.
|
|
687
|
+
*
|
|
688
|
+
* Updates the existing dc:identifier element referenced by the unique identifier id if one exists.
|
|
689
|
+
* Otherwise creates a new element with the provided identifier, and sets the unique identifier id to the new element's id.
|
|
690
|
+
*
|
|
691
|
+
* Note: you likely shouldn't change the unique identifier id unless you are producing a new EPUB.
|
|
692
|
+
*
|
|
693
|
+
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
|
|
694
|
+
*/
|
|
695
|
+
async setUniqueIdentifier(identifier) {
|
|
696
|
+
await this.withPackage(async (packageElement) => {
|
|
697
|
+
const metadata = Epub.findXmlChildByName(
|
|
698
|
+
"metadata",
|
|
699
|
+
Epub.getXmlChildren(packageElement)
|
|
700
|
+
);
|
|
701
|
+
if (!metadata) {
|
|
702
|
+
throw new Error(
|
|
703
|
+
"Failed to parse EPUB: found no metadata element in package document"
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
let uniqueId = await this.getUniqueIdentifierId();
|
|
707
|
+
if (!uniqueId) {
|
|
708
|
+
const newUniqueId = (0, import_nanoid.nanoid)();
|
|
709
|
+
packageElement[":@"] = {
|
|
710
|
+
...packageElement[":@"],
|
|
711
|
+
"@_unique-identifier": newUniqueId
|
|
712
|
+
};
|
|
713
|
+
uniqueId = newUniqueId;
|
|
714
|
+
}
|
|
715
|
+
const children = Epub.getXmlChildren(metadata);
|
|
716
|
+
const entry = Epub.findXmlChildByName(
|
|
717
|
+
"dc:identifier",
|
|
718
|
+
children,
|
|
719
|
+
(node) => {
|
|
720
|
+
var _a;
|
|
721
|
+
return ((_a = node[":@"]) == null ? void 0 : _a["@_id"]) === uniqueId;
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
if (entry) {
|
|
725
|
+
children.splice(
|
|
726
|
+
children.indexOf(entry),
|
|
727
|
+
1,
|
|
728
|
+
Epub.createXmlElement("dc:identifier", { id: uniqueId }, [
|
|
729
|
+
Epub.createXmlTextNode(identifier)
|
|
730
|
+
])
|
|
731
|
+
);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
children.push(
|
|
735
|
+
Epub.createXmlElement("dc:identifier", { id: uniqueId }, [
|
|
736
|
+
Epub.createXmlTextNode(identifier)
|
|
737
|
+
])
|
|
738
|
+
);
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Retrieve the id of the publication's unique identifier, as declared by the
|
|
743
|
+
* package element's `unique-identifier` attribute.
|
|
744
|
+
*
|
|
745
|
+
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
|
|
746
|
+
*/
|
|
747
|
+
async getUniqueIdentifierId() {
|
|
748
|
+
var _a;
|
|
749
|
+
const packageElement = await this.getPackageElement();
|
|
750
|
+
return ((_a = packageElement[":@"]) == null ? void 0 : _a["@_unique-identifier"]) ?? null;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Collect `dc:identifier` or `dc:source` entries, attaching the value and
|
|
754
|
+
* scheme of any refining `identifier-type` meta (spec D.3.8) and a legacy
|
|
755
|
+
* `opf:scheme` attribute. Values are not interpreted.
|
|
756
|
+
*
|
|
757
|
+
* `onRefinement` is invoked for every other meta refining a collected entry,
|
|
758
|
+
* so callers can surface element-specific refinements (e.g. `source-of` on a
|
|
759
|
+
* `dc:source`).
|
|
760
|
+
*/
|
|
761
|
+
static collectDcEntries(metadata, type, onRefinement) {
|
|
762
|
+
const entries = metadata.filter((entry) => entry.type === type && entry.value !== void 0).map((entry) => ({
|
|
763
|
+
// filtered above, so value is defined
|
|
764
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
765
|
+
value: entry.value,
|
|
766
|
+
...entry.id && { id: entry.id },
|
|
767
|
+
...entry.properties["opf:scheme"] && {
|
|
768
|
+
scheme: entry.properties["opf:scheme"]
|
|
769
|
+
}
|
|
770
|
+
}));
|
|
771
|
+
for (const meta of metadata) {
|
|
772
|
+
if (meta.type !== "meta" || meta.value === void 0) continue;
|
|
773
|
+
const property = meta.properties["property"];
|
|
774
|
+
const refines = meta.properties["refines"];
|
|
775
|
+
if (!property || !refines) continue;
|
|
776
|
+
const target = entries.find((t) => t.id === refines.slice(1));
|
|
777
|
+
if (!target) continue;
|
|
778
|
+
if (property === "identifier-type") {
|
|
779
|
+
target.identifierType = meta.value;
|
|
780
|
+
if (meta.properties["scheme"]) target.scheme = meta.properties["scheme"];
|
|
781
|
+
} else {
|
|
782
|
+
onRefinement == null ? void 0 : onRefinement(target, property, meta.value);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return entries;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Retrieve every `dc:identifier` entry, returned as found.
|
|
789
|
+
*
|
|
790
|
+
* Values are not interpreted. Any refining `identifier-type` meta (spec
|
|
791
|
+
* D.3.8) or legacy `opf:scheme` attribute is surfaced on the entry, but no
|
|
792
|
+
* parsing of the value itself is attempted. To read `dc:source` entries, use
|
|
793
|
+
* {@link getSources}.
|
|
794
|
+
*
|
|
795
|
+
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
|
|
796
|
+
*/
|
|
797
|
+
async getIdentifiers() {
|
|
798
|
+
const metadata = await this.getMetadata();
|
|
799
|
+
return Epub.collectDcEntries(metadata, "dc:identifier");
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Retrieve every `dc:source` entry, returned as found.
|
|
803
|
+
*
|
|
804
|
+
* Like {@link getIdentifiers}, values are not interpreted. In addition to a
|
|
805
|
+
* refining `identifier-type`, a refining `source-of` meta (spec D.3.11) is
|
|
806
|
+
* surfaced as `sourceOf`.
|
|
807
|
+
*
|
|
808
|
+
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcsource
|
|
809
|
+
*/
|
|
810
|
+
async getSources() {
|
|
811
|
+
const metadata = await this.getMetadata();
|
|
812
|
+
return Epub.collectDcEntries(
|
|
813
|
+
metadata,
|
|
814
|
+
"dc:source",
|
|
815
|
+
(source, property) => {
|
|
816
|
+
if (property === "source-of") {
|
|
817
|
+
source.isPageBreakSource = true;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Retrieve the `pageBreakSource` property (EPUB 3.4, spec D.2.9), the
|
|
824
|
+
* publication-level source for the source of its page break markers.
|
|
825
|
+
*
|
|
826
|
+
* This property replaces the refining `source-of="pagination"` meta (spec
|
|
827
|
+
* D.3.11), see {@link EpubSource.sourceOf}. If no `pageBreakSource` property is found,
|
|
828
|
+
* we fall back to finding a `dc:source` with a `source-of="pagination"` refinement.
|
|
829
|
+
*
|
|
830
|
+
* @link https://www.w3.org/TR/epub/#pageBreakSource
|
|
831
|
+
*/
|
|
832
|
+
async getPageBreakSource() {
|
|
833
|
+
const entry = await this.findMetadataItem(
|
|
834
|
+
(item) => item.type === "meta" && item.properties["property"] === "pageBreakSource" && !!item.value
|
|
835
|
+
);
|
|
836
|
+
if (entry) {
|
|
837
|
+
return {
|
|
838
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
839
|
+
value: entry.value,
|
|
840
|
+
id: entry.id,
|
|
841
|
+
identifierType: void 0,
|
|
842
|
+
scheme: void 0,
|
|
843
|
+
isPageBreakSource: true
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
const sources = await this.getSources();
|
|
847
|
+
const source = sources.find((source2) => source2.isPageBreakSource);
|
|
848
|
+
if (source) {
|
|
849
|
+
return source;
|
|
850
|
+
}
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Set the `pageBreakSource` property (EPUB 3.4, spec D.2.9), or remove it when
|
|
855
|
+
* passed null. Replaces an existing `pageBreakSource` meta if present.
|
|
856
|
+
*
|
|
857
|
+
* Pass `none` to indicate the pagination is unique to this publication.
|
|
858
|
+
*
|
|
859
|
+
* @link https://www.w3.org/TR/epub/#pageBreakSource
|
|
860
|
+
*/
|
|
861
|
+
async setPageBreakSource(value) {
|
|
862
|
+
if (value === null) {
|
|
863
|
+
await this.removeMetadata(
|
|
864
|
+
(item) => item.properties["property"] === "pageBreakSource"
|
|
865
|
+
);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
await this.replaceMetadata(
|
|
869
|
+
(item) => item.properties["property"] === "pageBreakSource",
|
|
870
|
+
{ type: "meta", properties: { property: "pageBreakSource" }, value }
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Replace the publication's `dc:identifier` entries.
|
|
875
|
+
*
|
|
876
|
+
* This replaces ALL existing `dc:identifier` elements except the publication's
|
|
877
|
+
* unique identifier (the one referenced by the package element's
|
|
878
|
+
* `unique-identifier` attribute), which is always preserved and must not be
|
|
879
|
+
* included in the provided list. If included anyway, it is ignored. See
|
|
880
|
+
* {@link setUniqueIdentifier} to change it.
|
|
881
|
+
*
|
|
882
|
+
* Identifiers are placed in the order they are provided.
|
|
883
|
+
*
|
|
884
|
+
* `dc:source` entries are not touched, use {@link setSources} for those.
|
|
885
|
+
*
|
|
886
|
+
* When an entry has an `identifierType`, it is written in the refining form
|
|
887
|
+
* (a `meta` with `property="identifier-type"`, carrying the `scheme`
|
|
888
|
+
* attribute when provided). An entry with only a `scheme` is written using
|
|
889
|
+
* the legacy `opf:scheme` attribute.
|
|
890
|
+
*
|
|
891
|
+
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
|
|
892
|
+
*/
|
|
893
|
+
async setIdentifiers(identifiers) {
|
|
894
|
+
const uniqueId = await this.getUniqueIdentifierId();
|
|
895
|
+
await this.withPackage((packageElement) => {
|
|
896
|
+
var _a;
|
|
897
|
+
const metadata = Epub.findXmlChildByName(
|
|
898
|
+
"metadata",
|
|
899
|
+
Epub.getXmlChildren(packageElement)
|
|
900
|
+
);
|
|
901
|
+
if (!metadata)
|
|
902
|
+
throw new Error(
|
|
903
|
+
"Failed to parse EPUB: found no metadata element in package document"
|
|
904
|
+
);
|
|
905
|
+
const children = Epub.getXmlChildren(metadata);
|
|
906
|
+
const removedIds = /* @__PURE__ */ new Set();
|
|
907
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
908
|
+
const node = children[i];
|
|
909
|
+
if (Epub.isXmlTextNode(node) || Epub.getXmlElementName(node) !== "dc:identifier") {
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
const id = (_a = node[":@"]) == null ? void 0 : _a["@_id"];
|
|
913
|
+
if (id && id === uniqueId) {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
if (id) {
|
|
917
|
+
removedIds.add(id);
|
|
918
|
+
}
|
|
919
|
+
children.splice(i, 1);
|
|
920
|
+
}
|
|
921
|
+
Epub.removeRefiningMetas(children, removedIds, ["identifier-type"]);
|
|
922
|
+
for (const identifier of identifiers) {
|
|
923
|
+
if (identifier.id && identifier.id === uniqueId) continue;
|
|
924
|
+
const id = identifier.id ?? (identifier.identifierType !== void 0 ? (0, import_nanoid.nanoid)() : void 0);
|
|
925
|
+
children.push(
|
|
926
|
+
Epub.createXmlElement(
|
|
927
|
+
"dc:identifier",
|
|
928
|
+
{
|
|
929
|
+
...id && { id },
|
|
930
|
+
...identifier.scheme && identifier.identifierType === void 0 && {
|
|
931
|
+
"opf:scheme": identifier.scheme
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
[Epub.createXmlTextNode(identifier.value)]
|
|
935
|
+
)
|
|
936
|
+
);
|
|
937
|
+
if (identifier.identifierType !== void 0 && id) {
|
|
938
|
+
children.push(
|
|
939
|
+
Epub.createXmlElement(
|
|
940
|
+
"meta",
|
|
941
|
+
{
|
|
942
|
+
refines: `#${id}`,
|
|
943
|
+
property: "identifier-type",
|
|
944
|
+
...identifier.scheme && { scheme: identifier.scheme }
|
|
945
|
+
},
|
|
946
|
+
[Epub.createXmlTextNode(identifier.identifierType)]
|
|
947
|
+
)
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Remove `meta` refinements pointing at any of the given ids,
|
|
955
|
+
* restricted to the given `property` values as a cleanup step
|
|
956
|
+
* Mutates `children` in place.
|
|
957
|
+
*/
|
|
958
|
+
static removeRefiningMetas(children, ids, properties) {
|
|
959
|
+
var _a, _b, _c;
|
|
960
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
961
|
+
const node = children[i];
|
|
962
|
+
if (Epub.isXmlTextNode(node) || Epub.getXmlElementName(node) !== "meta") {
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const property = (_a = node[":@"]) == null ? void 0 : _a["@_property"];
|
|
966
|
+
if (!property || !properties.includes(property)) continue;
|
|
967
|
+
const refines = (_c = (_b = node[":@"]) == null ? void 0 : _b["@_refines"]) == null ? void 0 : _c.slice(1);
|
|
968
|
+
if (refines && ids.has(refines)) {
|
|
969
|
+
children.splice(i, 1);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Replace the publication's `dc:source` entries.
|
|
975
|
+
*
|
|
976
|
+
* This replaces ALL existing `dc:source` elements (and their refining
|
|
977
|
+
* `identifier-type` / `source-of` metas). `dc:identifier` entries are not
|
|
978
|
+
* touched; use {@link setIdentifiers} for those. Pass an empty array to
|
|
979
|
+
* remove all sources.
|
|
980
|
+
*
|
|
981
|
+
* When an entry has an `identifierType`, it is written in the refining form.
|
|
982
|
+
* A `sourceOf` value is written as a refining `source-of` meta (spec D.3.11).
|
|
983
|
+
* An entry with only a `scheme` uses the legacy `opf:scheme` attribute.
|
|
984
|
+
*
|
|
985
|
+
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcsource
|
|
986
|
+
*/
|
|
987
|
+
async setSources(sources) {
|
|
988
|
+
await this.withPackage((packageElement) => {
|
|
989
|
+
var _a;
|
|
990
|
+
const metadata = Epub.findXmlChildByName(
|
|
991
|
+
"metadata",
|
|
992
|
+
Epub.getXmlChildren(packageElement)
|
|
993
|
+
);
|
|
994
|
+
if (!metadata)
|
|
995
|
+
throw new Error(
|
|
996
|
+
"Failed to parse EPUB: found no metadata element in package document"
|
|
997
|
+
);
|
|
998
|
+
const children = Epub.getXmlChildren(metadata);
|
|
999
|
+
const removedIds = /* @__PURE__ */ new Set();
|
|
1000
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
1001
|
+
const node = children[i];
|
|
1002
|
+
if (Epub.isXmlTextNode(node) || Epub.getXmlElementName(node) !== "dc:source") {
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
const id = (_a = node[":@"]) == null ? void 0 : _a["@_id"];
|
|
1006
|
+
if (id) {
|
|
1007
|
+
removedIds.add(id);
|
|
1008
|
+
}
|
|
1009
|
+
children.splice(i, 1);
|
|
1010
|
+
}
|
|
1011
|
+
Epub.removeRefiningMetas(children, removedIds, [
|
|
1012
|
+
"identifier-type",
|
|
1013
|
+
"source-of"
|
|
1014
|
+
]);
|
|
1015
|
+
for (const source of sources) {
|
|
1016
|
+
const needsId = source.identifierType !== void 0 || source.isPageBreakSource;
|
|
1017
|
+
const id = source.id ?? (needsId ? (0, import_nanoid.nanoid)() : void 0);
|
|
1018
|
+
children.push(
|
|
1019
|
+
Epub.createXmlElement(
|
|
1020
|
+
"dc:source",
|
|
1021
|
+
{
|
|
1022
|
+
...id && { id },
|
|
1023
|
+
...source.scheme && source.identifierType === void 0 && {
|
|
1024
|
+
"opf:scheme": source.scheme
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
[Epub.createXmlTextNode(source.value)]
|
|
1028
|
+
)
|
|
1029
|
+
);
|
|
1030
|
+
if (source.identifierType !== void 0 && id) {
|
|
1031
|
+
children.push(
|
|
1032
|
+
Epub.createXmlElement(
|
|
1033
|
+
"meta",
|
|
1034
|
+
{
|
|
1035
|
+
refines: `#${id}`,
|
|
1036
|
+
property: "identifier-type",
|
|
1037
|
+
...source.scheme && { scheme: source.scheme }
|
|
1038
|
+
},
|
|
1039
|
+
[Epub.createXmlTextNode(source.identifierType)]
|
|
1040
|
+
)
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
if (source.isPageBreakSource && id) {
|
|
1044
|
+
children.push(
|
|
1045
|
+
Epub.createXmlElement(
|
|
1046
|
+
"meta",
|
|
1047
|
+
{ refines: `#${id}`, property: "source-of" },
|
|
1048
|
+
[Epub.createXmlTextNode("pagination")]
|
|
1049
|
+
)
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
612
1055
|
/**
|
|
613
1056
|
* Even "EPUB 3" publications sometimes still only use the
|
|
614
1057
|
* EPUB 2 specification for identifying the cover image.
|
|
@@ -681,11 +1124,12 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
681
1124
|
* the provided href within the publication.
|
|
682
1125
|
*/
|
|
683
1126
|
async setCoverImage(href, data) {
|
|
1127
|
+
var _a;
|
|
684
1128
|
const coverImageItem = await this.getCoverImageItem();
|
|
685
1129
|
if (coverImageItem) {
|
|
686
1130
|
await this.removeManifestItem(coverImageItem.id);
|
|
687
1131
|
}
|
|
688
|
-
const mediaType = (
|
|
1132
|
+
const mediaType = (_a = import_media_types.MediaType.fromPath(href)) == null ? void 0 : _a.mime;
|
|
689
1133
|
if (!mediaType)
|
|
690
1134
|
throw new Error(`Invalid file extension for cover image: ${href}`);
|
|
691
1135
|
await this.addManifestItem(
|
|
@@ -1664,35 +2108,81 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
1664
2108
|
return this.getNavigation("page-list", { resolveToRoot });
|
|
1665
2109
|
}
|
|
1666
2110
|
/**
|
|
1667
|
-
*
|
|
2111
|
+
* Name the entry a resolved URL addresses.
|
|
2112
|
+
*
|
|
2113
|
+
* A correctly authored publication percent-encodes its hrefs, so the
|
|
2114
|
+
* decoded reading is the one the spec calls for and the one we prefer.
|
|
2115
|
+
* Some publications instead write the entry name verbatim, so when the two
|
|
2116
|
+
* readings differ we ask the archive which one it holds. A verbatim name
|
|
2117
|
+
* carrying a bare `%`, as `100%.xhtml` does, is not a valid encoding at all
|
|
2118
|
+
* and can only be its own answer.
|
|
1668
2119
|
*/
|
|
1669
|
-
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
2120
|
+
async entryForUrl(url) {
|
|
2121
|
+
const asWritten = url.pathname.slice(1);
|
|
2122
|
+
let decoded;
|
|
2123
|
+
try {
|
|
2124
|
+
decoded = decodeURIComponent(asWritten);
|
|
2125
|
+
} catch {
|
|
2126
|
+
return asWritten;
|
|
2127
|
+
}
|
|
2128
|
+
if (decoded === asWritten) {
|
|
2129
|
+
return decoded;
|
|
2130
|
+
}
|
|
2131
|
+
if (await this.adapter.exists(decoded)) {
|
|
2132
|
+
return decoded;
|
|
2133
|
+
}
|
|
2134
|
+
return await this.adapter.exists(asWritten) ? asWritten : decoded;
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Resolve an href to the name of the archive entry it addresses.
|
|
2138
|
+
*
|
|
2139
|
+
* @param from The entry the href appears in; it resolves against that
|
|
2140
|
+
* entry's directory
|
|
2141
|
+
* @throws when the href addresses something outside the publication
|
|
2142
|
+
*/
|
|
2143
|
+
async resolveEntry(from, href) {
|
|
2144
|
+
const url = resolveEntryUrl(from, href);
|
|
2145
|
+
if (!url) {
|
|
2146
|
+
throw new Error(
|
|
2147
|
+
`href does not address an entry in this publication: ${href}`
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
return this.entryForUrl(url);
|
|
1676
2151
|
}
|
|
1677
2152
|
/**
|
|
1678
2153
|
* Returns a path-relative-scheme-less URL, relative to the
|
|
1679
2154
|
* container root.
|
|
1680
2155
|
*
|
|
2156
|
+
* An href that points outside the publication, such as an external link in
|
|
2157
|
+
* a navigation document, is handed back unchanged.
|
|
2158
|
+
*
|
|
1681
2159
|
* @param href The href to resolve
|
|
1682
2160
|
* @param [relativeTo] Optional - The href to resolve this href relative to.
|
|
1683
2161
|
Use if resolving a relative href from a file other than the package document.
|
|
1684
2162
|
*/
|
|
1685
2163
|
async resolveHref(href, relativeTo, { toRoot } = {}) {
|
|
2164
|
+
if (href.startsWith("#")) return href;
|
|
1686
2165
|
const rootfile = await this.getRootfile();
|
|
1687
|
-
const from = relativeTo ? this.
|
|
1688
|
-
const
|
|
1689
|
-
|
|
2166
|
+
const from = relativeTo ? await this.resolveEntry(rootfile, relativeTo) : rootfile;
|
|
2167
|
+
const url = resolveEntryUrl(from, href);
|
|
2168
|
+
if (!url) return href;
|
|
2169
|
+
const entry = (await this.entryForUrl(url)).split("/");
|
|
2170
|
+
const base = toRoot ? [] : rootfile.split("/").slice(0, -1);
|
|
2171
|
+
let shared = 0;
|
|
2172
|
+
while (shared < base.length && base[shared] === entry[shared]) {
|
|
2173
|
+
shared++;
|
|
2174
|
+
}
|
|
2175
|
+
const relative = [
|
|
2176
|
+
...Array(base.length - shared).fill(".."),
|
|
2177
|
+
...entry.slice(shared).map(encodeURIComponent)
|
|
2178
|
+
];
|
|
2179
|
+
return relative.join("/") + url.hash;
|
|
1690
2180
|
}
|
|
1691
2181
|
async readFileContents(href, relativeTo, encoding) {
|
|
1692
2182
|
const rootfile = await this.getRootfile();
|
|
1693
|
-
const from = relativeTo ? this.
|
|
1694
|
-
const
|
|
1695
|
-
const itemEntry = encoding ? await this.
|
|
2183
|
+
const from = relativeTo ? await this.resolveEntry(rootfile, relativeTo) : rootfile;
|
|
2184
|
+
const entry = await this.resolveEntry(from, href);
|
|
2185
|
+
const itemEntry = encoding ? await this.adapter.read(entry, encoding) : await this.adapter.read(entry);
|
|
1696
2186
|
return itemEntry;
|
|
1697
2187
|
}
|
|
1698
2188
|
async readItemContents(id, encoding) {
|
|
@@ -1701,8 +2191,8 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
1701
2191
|
const manifestItem = manifest[id];
|
|
1702
2192
|
if (!manifestItem)
|
|
1703
2193
|
throw new Error(`Could not find item with id "${id}" in manifest`);
|
|
1704
|
-
const
|
|
1705
|
-
const itemEntry = encoding ? await this.
|
|
2194
|
+
const entry = await this.resolveEntry(rootfile, manifestItem.href);
|
|
2195
|
+
const itemEntry = encoding ? await this.adapter.read(entry, encoding) : await this.adapter.read(entry);
|
|
1706
2196
|
return itemEntry;
|
|
1707
2197
|
}
|
|
1708
2198
|
/**
|
|
@@ -1760,11 +2250,11 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
1760
2250
|
if (!manifestItem)
|
|
1761
2251
|
throw new Error(`Could not find item with id "${id}" in manifest`);
|
|
1762
2252
|
import_mem.default.clear(this.readXhtmlItemContents);
|
|
1763
|
-
const
|
|
2253
|
+
const entry = await this.resolveEntry(rootfile, manifestItem.href);
|
|
1764
2254
|
if (encoding === "utf-8") {
|
|
1765
|
-
await this.writeEntryContents(
|
|
2255
|
+
await this.writeEntryContents(entry, contents, encoding);
|
|
1766
2256
|
} else {
|
|
1767
|
-
await this.writeEntryContents(
|
|
2257
|
+
await this.writeEntryContents(entry, contents);
|
|
1768
2258
|
}
|
|
1769
2259
|
}
|
|
1770
2260
|
/**
|
|
@@ -1835,7 +2325,7 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
1835
2325
|
});
|
|
1836
2326
|
this.manifest = null;
|
|
1837
2327
|
const rootfile = await this.getRootfile();
|
|
1838
|
-
const filename = this.
|
|
2328
|
+
const filename = await this.resolveEntry(rootfile, item.href);
|
|
1839
2329
|
const data = encoding === "utf-8" || encoding === "xml" ? new TextEncoder().encode(
|
|
1840
2330
|
encoding === "utf-8" ? contents : await Epub.xmlBuilder.build(
|
|
1841
2331
|
contents
|
|
@@ -2019,10 +2509,7 @@ ${JSON.stringify(element, null, 2)}`
|
|
|
2019
2509
|
);
|
|
2020
2510
|
const spineTocId = (_a = spine == null ? void 0 : spine[":@"]) == null ? void 0 : _a["@_toc"];
|
|
2021
2511
|
const ncxItem = spineTocId ? manifest[spineTocId] : Object.values(manifest).find(
|
|
2022
|
-
(item) =>
|
|
2023
|
-
var _a2;
|
|
2024
|
-
return ((_a2 = item.mediaType) == null ? void 0 : _a2.toLowerCase()) === "application/x-dtbncx+xml";
|
|
2025
|
-
}
|
|
2512
|
+
(item) => import_media_types.MediaType.fromMime(item.mediaType ?? "") === import_media_types.MediaType.NCX
|
|
2026
2513
|
);
|
|
2027
2514
|
if (!ncxItem) return [];
|
|
2028
2515
|
const ncxContent = await this.readItemContents(ncxItem.id, "utf-8");
|
|
@@ -2163,7 +2650,12 @@ class EpubFactory {
|
|
|
2163
2650
|
"This is not a valid EPUB publication. Could not read the package document."
|
|
2164
2651
|
);
|
|
2165
2652
|
}
|
|
2166
|
-
|
|
2653
|
+
try {
|
|
2654
|
+
await Epub.assertEpub3(epub);
|
|
2655
|
+
} catch (error) {
|
|
2656
|
+
epub.discardAndClose();
|
|
2657
|
+
throw error;
|
|
2658
|
+
}
|
|
2167
2659
|
return epub;
|
|
2168
2660
|
}
|
|
2169
2661
|
/**
|
|
@@ -2203,14 +2695,11 @@ class EpubFactory {
|
|
|
2203
2695
|
const container = encoder.encode(`<?xml version="1.0"?>
|
|
2204
2696
|
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
|
2205
2697
|
<rootfiles>
|
|
2206
|
-
<rootfile media-type="
|
|
2698
|
+
<rootfile media-type="${import_media_types.MediaType.OPF.mime}" full-path="OEBPS/content.opf"/>
|
|
2207
2699
|
</rootfiles>
|
|
2208
2700
|
</container>
|
|
2209
2701
|
`);
|
|
2210
|
-
await adapter.write(
|
|
2211
|
-
(0, import_path.join)(adapter.rootPath, "META-INF", "container.xml"),
|
|
2212
|
-
container
|
|
2213
|
-
);
|
|
2702
|
+
await adapter.write("META-INF/container.xml", container);
|
|
2214
2703
|
const packageDocument = encoder.encode(`<?xml version="1.0"?>
|
|
2215
2704
|
<package unique-identifier="pub-id" dir="${language.textInfo.direction}" xml:lang="${language.toString()}" version="3.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
2216
2705
|
<metadata>
|
|
@@ -2221,10 +2710,7 @@ class EpubFactory {
|
|
|
2221
2710
|
</spine>
|
|
2222
2711
|
</package>
|
|
2223
2712
|
`);
|
|
2224
|
-
await adapter.write(
|
|
2225
|
-
(0, import_path.join)(adapter.rootPath, "OEBPS", "content.opf"),
|
|
2226
|
-
packageDocument
|
|
2227
|
-
);
|
|
2713
|
+
await adapter.write("OEBPS/content.opf", packageDocument);
|
|
2228
2714
|
const epub = new Epub(this.adapterClass, adapter, path);
|
|
2229
2715
|
const metadata = [
|
|
2230
2716
|
{
|
|
@@ -2273,7 +2759,6 @@ class EpubFactory {
|
|
|
2273
2759
|
* @throws when the adapter is read-only
|
|
2274
2760
|
*/
|
|
2275
2761
|
async upgrade(path, options = {}) {
|
|
2276
|
-
var _a;
|
|
2277
2762
|
if (!this.adapterClass.capabilities.writable) {
|
|
2278
2763
|
throw new EpubReadOnlyError(
|
|
2279
2764
|
`adapter ${this.adapterClass.kind} is read-only; cannot upgrade`
|
|
@@ -2299,49 +2784,55 @@ class EpubFactory {
|
|
|
2299
2784
|
"This is not a valid EPUB publication. Could not read the package document."
|
|
2300
2785
|
);
|
|
2301
2786
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2787
|
+
try {
|
|
2788
|
+
const version = await epub.getVersion();
|
|
2789
|
+
if (version.startsWith("3.")) {
|
|
2790
|
+
return epub;
|
|
2791
|
+
}
|
|
2792
|
+
const tocEntries = await epub.getNcxTableOfContents();
|
|
2793
|
+
let landmarks = [];
|
|
2794
|
+
await epub.withPackage((pkg) => {
|
|
2795
|
+
landmarks = Upgrade.extractGuideLandmarks(pkg);
|
|
2796
|
+
Upgrade.upgradePackageMetadata(pkg);
|
|
2797
|
+
Upgrade.fixFontMimeTypes(pkg);
|
|
2798
|
+
Upgrade.removeGuide(pkg);
|
|
2799
|
+
if (removeNcx) {
|
|
2800
|
+
Upgrade.removeSpineTocRef(pkg);
|
|
2801
|
+
}
|
|
2802
|
+
Upgrade.setPackageVersion(pkg, "3.0");
|
|
2803
|
+
});
|
|
2804
|
+
await Upgrade.collectManifestProperties(epub);
|
|
2313
2805
|
if (removeNcx) {
|
|
2314
|
-
Upgrade.
|
|
2806
|
+
await Upgrade.removeNcx(epub);
|
|
2315
2807
|
}
|
|
2316
|
-
Upgrade.
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2808
|
+
const navHref = await Upgrade.chooseNavHref(epub);
|
|
2809
|
+
const navContent = await Upgrade.buildNavDocument(
|
|
2810
|
+
epub,
|
|
2811
|
+
tocEntries,
|
|
2812
|
+
landmarks
|
|
2813
|
+
);
|
|
2814
|
+
await epub.addManifestItem(
|
|
2815
|
+
{
|
|
2816
|
+
id: "nav",
|
|
2817
|
+
href: navHref,
|
|
2818
|
+
mediaType: import_media_types.MediaType.XHTML.mime,
|
|
2819
|
+
properties: ["nav"]
|
|
2820
|
+
},
|
|
2821
|
+
navContent,
|
|
2822
|
+
"utf-8"
|
|
2823
|
+
);
|
|
2824
|
+
const manifest = await epub.getManifest();
|
|
2825
|
+
for (const item of Object.values(manifest)) {
|
|
2826
|
+
if (import_media_types.MediaType.fromMime(item.mediaType ?? "") !== import_media_types.MediaType.XHTML)
|
|
2827
|
+
continue;
|
|
2828
|
+
const contents = await epub.readXhtmlItemContents(item.id);
|
|
2829
|
+
await epub.writeXhtmlItemContents(item.id, contents);
|
|
2830
|
+
}
|
|
2831
|
+
return epub;
|
|
2832
|
+
} catch (error) {
|
|
2833
|
+
epub.discardAndClose();
|
|
2834
|
+
throw error;
|
|
2343
2835
|
}
|
|
2344
|
-
return epub;
|
|
2345
2836
|
}
|
|
2346
2837
|
}
|
|
2347
2838
|
// Annotate the CommonJS export names for ESM import in node:
|