@prose-reader/archive-parser 1.287.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/apple/parse.d.ts +14 -0
- package/dist/apple/parse.test.d.ts +1 -0
- package/dist/apple/resolve.d.ts +3 -0
- package/dist/apple/resolve.test.d.ts +1 -0
- package/dist/comicInfo/manga.d.ts +8 -0
- package/dist/comicInfo/manga.test.d.ts +1 -0
- package/dist/comicInfo/parse.d.ts +66 -0
- package/dist/comicInfo/parse.test.d.ts +1 -0
- package/dist/comicInfo/resolve.d.ts +3 -0
- package/dist/comicInfo/resolve.test.d.ts +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.cjs +2 -0
- package/dist/index.umd.cjs.map +1 -0
- package/dist/kobo/parse.d.ts +14 -0
- package/dist/kobo/parse.test.d.ts +1 -0
- package/dist/kobo/resolve.d.ts +3 -0
- package/dist/kobo/resolve.test.d.ts +1 -0
- package/dist/opf/parse.d.ts +50 -0
- package/dist/opf/parse.test.d.ts +1 -0
- package/dist/opf/resolve.d.ts +3 -0
- package/dist/opf/resolve.test.d.ts +1 -0
- package/dist/opf/spineItemrefProperties.d.ts +11 -0
- package/dist/opf/spineItemrefProperties.test.d.ts +1 -0
- package/dist/resolve.d.ts +7 -0
- package/dist/types/archiveResolve.d.ts +13 -0
- package/dist/utils/normalizeGtin.d.ts +5 -0
- package/dist/utils/normalizeGtin.test.d.ts +1 -0
- package/dist/utils/normalizeIsbn.d.ts +11 -0
- package/dist/utils/tokenizeXmlSpaceSeparatedList.d.ts +5 -0
- package/dist/utils/tokenizeXmlSpaceSeparatedList.test.d.ts +1 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @oboku/archive-metadata
|
|
2
|
+
|
|
3
|
+
This package purpose is to help with two things:
|
|
4
|
+
|
|
5
|
+
- Make common archive configuration files typed and easy to read (typescript + JSON like object)
|
|
6
|
+
- Help resolve different archive configuration into a common interpretation
|
|
7
|
+
|
|
8
|
+
Additionally it offers a fast and platform agnostic parser (web, node). This is an opinionated decision. The library used @xmldom/xmldom is fast and small.
|
|
9
|
+
|
|
10
|
+
This package is not trying to become a standard, it is mostly used internally to help translate and normalize different book providers (or even different version).
|
|
11
|
+
|
|
12
|
+
Essentially, instead of implementing your own parser for "foo.xml" and trying to understand what is what, we offer a common parsing and resolution.
|
|
13
|
+
|
|
14
|
+
Example of products that may take advantage of this package:
|
|
15
|
+
- App that lets user manipulate book archives
|
|
16
|
+
- Reading app
|
|
17
|
+
- App that handle book metadata
|
|
18
|
+
|
|
19
|
+
## ComicInfo.xml
|
|
20
|
+
|
|
21
|
+
We are following https://anansi-project.github.io/
|
|
22
|
+
|
|
23
|
+
## Epub
|
|
24
|
+
|
|
25
|
+
Spec for 3.3 is available at https://www.w3.org/TR/epub-33/
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const APPLE_IBOOKS_DISPLAY_OPTIONS_FILENAME = "com.apple.ibooks.display-options.xml";
|
|
2
|
+
export type AppleDisplayOption = {
|
|
3
|
+
readonly name?: string;
|
|
4
|
+
readonly value: string;
|
|
5
|
+
};
|
|
6
|
+
export type AppleMetadata = {
|
|
7
|
+
readonly kind: "apple";
|
|
8
|
+
readonly displayOptions?: {
|
|
9
|
+
readonly platform?: {
|
|
10
|
+
readonly options: ReadonlyArray<AppleDisplayOption>;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export declare const parseAppleDisplayOptionsXml: (xml: string) => AppleMetadata;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowed `Manga` element values (ComicInfo 2.0 `Manga` simpleType).
|
|
3
|
+
*
|
|
4
|
+
* @see https://anansi-project.github.io/docs/comicinfo/documentation#manga
|
|
5
|
+
*/
|
|
6
|
+
export declare const COMIC_INFO_MANGA_VALUES: readonly ["Unknown", "No", "Yes", "YesAndRightToLeft"];
|
|
7
|
+
export type ComicInfoManga = (typeof COMIC_INFO_MANGA_VALUES)[number];
|
|
8
|
+
export declare const isComicInfoManga: (value: string) => value is ComicInfoManga;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ComicInfoManga } from './manga';
|
|
2
|
+
/** Canonical top-level filename; real archives may use any casing. */
|
|
3
|
+
export declare const COMIC_INFO_FILENAME = "ComicInfo.xml";
|
|
4
|
+
/**
|
|
5
|
+
* Parsed `ComicInfo.xml` root: one optional string property per child element,
|
|
6
|
+
* using the same names as in the file (e.g. `Title`, `GTIN`, `LanguageISO`).
|
|
7
|
+
* Nested blocks such as `Pages` are skipped. Other simple child elements are
|
|
8
|
+
* still copied onto this object under their tag name.
|
|
9
|
+
*
|
|
10
|
+
* @see https://anansi-project.github.io/docs/comicinfo/intro
|
|
11
|
+
* @see https://github.com/anansi-project/comicinfo/blob/main/drafts/v2.1/ComicInfo.xsd for schema
|
|
12
|
+
*/
|
|
13
|
+
export interface ComicInfo {
|
|
14
|
+
readonly kind: "comicInfo";
|
|
15
|
+
AgeRating?: string;
|
|
16
|
+
AlternateCount?: string;
|
|
17
|
+
AlternateNumber?: string;
|
|
18
|
+
AlternateSeries?: string;
|
|
19
|
+
BlackAndWhite?: string;
|
|
20
|
+
Characters?: string;
|
|
21
|
+
Colorist?: string;
|
|
22
|
+
CommunityRating?: string;
|
|
23
|
+
Count?: string;
|
|
24
|
+
CoverArtist?: string;
|
|
25
|
+
Day?: string;
|
|
26
|
+
Editor?: string;
|
|
27
|
+
Format?: string;
|
|
28
|
+
Genre?: string;
|
|
29
|
+
GTIN?: string;
|
|
30
|
+
Imprint?: string;
|
|
31
|
+
Inker?: string;
|
|
32
|
+
LanguageISO?: string;
|
|
33
|
+
Letterer?: string;
|
|
34
|
+
Locations?: string;
|
|
35
|
+
MainCharacterOrTeam?: string;
|
|
36
|
+
/** Schema literals per {@link ComicInfoManga}; files may still use other strings. */
|
|
37
|
+
Manga?: ComicInfoManga | (string & {});
|
|
38
|
+
Month?: string;
|
|
39
|
+
Notes?: string;
|
|
40
|
+
Number?: string;
|
|
41
|
+
PageCount?: string;
|
|
42
|
+
Penciller?: string;
|
|
43
|
+
Publisher?: string;
|
|
44
|
+
Review?: string;
|
|
45
|
+
ScanInformation?: string;
|
|
46
|
+
Series?: string;
|
|
47
|
+
SeriesGroup?: string;
|
|
48
|
+
StoryArc?: string;
|
|
49
|
+
StoryArcNumber?: string;
|
|
50
|
+
Summary?: string;
|
|
51
|
+
Tags?: string;
|
|
52
|
+
Teams?: string;
|
|
53
|
+
Title?: string;
|
|
54
|
+
Translator?: string;
|
|
55
|
+
Volume?: string;
|
|
56
|
+
Web?: string;
|
|
57
|
+
Writer?: string;
|
|
58
|
+
Year?: string;
|
|
59
|
+
[tag: string]: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse a raw `ComicInfo.xml` body. Each direct child element with plain text
|
|
63
|
+
* becomes a property named after that tag. Malformed XML throws; the parser
|
|
64
|
+
* error is attached as `cause`.
|
|
65
|
+
*/
|
|
66
|
+
export declare const parseComicInfo: (xml: string) => ComicInfo;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsers and resolvers for archive-embedded metadata (ComicInfo, Kobo XML, …).
|
|
3
|
+
* Parsed values carry a `kind` discriminator (`comicInfo` / `kobo` / `apple` / `opf`);
|
|
4
|
+
* {@link resolveArchiveMetadata} accepts that union directly.
|
|
5
|
+
*/
|
|
6
|
+
export type { AppleDisplayOption, AppleMetadata } from './apple/parse';
|
|
7
|
+
export { APPLE_IBOOKS_DISPLAY_OPTIONS_FILENAME, parseAppleDisplayOptionsXml, } from './apple/parse';
|
|
8
|
+
export type { ComicInfoManga } from './comicInfo/manga';
|
|
9
|
+
export { COMIC_INFO_MANGA_VALUES, isComicInfoManga } from './comicInfo/manga';
|
|
10
|
+
export type { ComicInfo } from './comicInfo/parse';
|
|
11
|
+
export { COMIC_INFO_FILENAME, parseComicInfo } from './comicInfo/parse';
|
|
12
|
+
export type { KoboMetadata } from './kobo/parse';
|
|
13
|
+
export { KOBO_DISPLAY_OPTIONS_FILENAME, parseKoboXml } from './kobo/parse';
|
|
14
|
+
export type { OpfGuideReference, OpfIdentifier, OpfMetadata, OpfSpineManifestItem, OpfSpineRow, } from './opf/parse';
|
|
15
|
+
export { parseOpf } from './opf/parse';
|
|
16
|
+
export type { ResolvedArchiveInput } from './resolve';
|
|
17
|
+
export { resolveArchiveMetadata } from './resolve';
|
|
18
|
+
export type { ArchiveResolveResult } from './types/archiveResolve';
|
|
19
|
+
export { normalizeGtin } from './utils/normalizeGtin';
|
|
20
|
+
export { normalizeIsbn } from './utils/normalizeIsbn';
|
|
21
|
+
export { tokenizeXmlSpaceSeparatedList } from './utils/tokenizeXmlSpaceSeparatedList';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { XmlDocument as l } from "xmldoc";
|
|
2
|
+
const W = "com.apple.ibooks.display-options.xml", k = (t) => t.childrenNamed("option").map((e) => ({
|
|
3
|
+
name: e.attr?.name,
|
|
4
|
+
value: e.val
|
|
5
|
+
})), Z = (t) => {
|
|
6
|
+
const e = new l(t);
|
|
7
|
+
if (e.name?.toLowerCase() !== "display_options")
|
|
8
|
+
return { kind: "apple" };
|
|
9
|
+
const n = e.childNamed("platform");
|
|
10
|
+
return {
|
|
11
|
+
kind: "apple",
|
|
12
|
+
displayOptions: {
|
|
13
|
+
...n !== void 0 ? { platform: { options: k(n) } } : {}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}, A = [
|
|
17
|
+
"Unknown",
|
|
18
|
+
"No",
|
|
19
|
+
"Yes",
|
|
20
|
+
"YesAndRightToLeft"
|
|
21
|
+
], ee = (t) => {
|
|
22
|
+
for (const e of A)
|
|
23
|
+
if (e === t) return !0;
|
|
24
|
+
return !1;
|
|
25
|
+
}, T = "ComicInfo.xml", _ = /* @__PURE__ */ new Set(["Pages", "ComicInfo"]), M = (t) => t.children.some((e) => e.type === "element"), D = (t) => {
|
|
26
|
+
const e = t.val.trim();
|
|
27
|
+
return e.length > 0 ? e : void 0;
|
|
28
|
+
}, te = (t) => {
|
|
29
|
+
let e;
|
|
30
|
+
try {
|
|
31
|
+
e = new l(t);
|
|
32
|
+
} catch (n) {
|
|
33
|
+
const r = n instanceof Error ? n.message : String(n);
|
|
34
|
+
throw new Error(`${T} is malformed: ${r}`, {
|
|
35
|
+
cause: n
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const o = {};
|
|
39
|
+
return e.eachChild((n) => {
|
|
40
|
+
if (n.type !== "element" || _.has(n.name) || M(n)) return;
|
|
41
|
+
const r = D(n);
|
|
42
|
+
r !== void 0 && o[n.name] === void 0 && (o[n.name] = r);
|
|
43
|
+
}), { kind: "comicInfo", ...o };
|
|
44
|
+
}, oe = "com.kobobooks.display-options.xml", P = (t) => {
|
|
45
|
+
const e = t.childNamed("platform");
|
|
46
|
+
if (!e) return {};
|
|
47
|
+
for (const o of e.childrenNamed("option"))
|
|
48
|
+
if (o.attr?.name === "fixed-layout")
|
|
49
|
+
return o.val.trim().toLowerCase() === "true" ? { renditionLayout: "pre-paginated" } : {};
|
|
50
|
+
return {};
|
|
51
|
+
}, ne = (t) => {
|
|
52
|
+
const e = new l(t);
|
|
53
|
+
return e.name?.toLowerCase() === "display_options" ? { kind: "kobo", ...P(e) } : { kind: "kobo" };
|
|
54
|
+
}, F = (t) => t === void 0 || t.trim().length === 0 ? [] : t.trim().split(/\s+/).filter((e) => e.length > 0), x = (t) => {
|
|
55
|
+
const e = F(t);
|
|
56
|
+
if (e.length === 0)
|
|
57
|
+
return {};
|
|
58
|
+
let o;
|
|
59
|
+
return e.includes("rendition:layout-reflowable") && (o = "reflowable"), e.includes("rendition:layout-pre-paginated") && (o = "pre-paginated"), {
|
|
60
|
+
...o !== void 0 ? { renditionLayout: o } : {},
|
|
61
|
+
...e.includes("page-spread-left") ? { pageSpreadLeft: !0 } : {},
|
|
62
|
+
...e.includes("page-spread-right") ? { pageSpreadRight: !0 } : {}
|
|
63
|
+
};
|
|
64
|
+
}, u = (t) => t.includes(":") ? t.slice(t.lastIndexOf(":") + 1) : t, I = (t, e) => u(t).toLowerCase() === e.toLowerCase(), w = (t) => t.type === "element", d = (t, e) => {
|
|
65
|
+
for (const o of t.children)
|
|
66
|
+
if (w(o) && I(o.name, e))
|
|
67
|
+
return o;
|
|
68
|
+
}, p = (t, e) => {
|
|
69
|
+
const o = [];
|
|
70
|
+
for (const n of t.children)
|
|
71
|
+
w(n) && I(n.name, e) && o.push(n);
|
|
72
|
+
return o;
|
|
73
|
+
}, R = (t) => {
|
|
74
|
+
const e = [];
|
|
75
|
+
return t.eachChild((o) => {
|
|
76
|
+
if (u(o.name).toLowerCase() !== "identifier") return;
|
|
77
|
+
const n = o.val.trim();
|
|
78
|
+
if (n.length === 0) return;
|
|
79
|
+
const i = (o.attr["opf:scheme"] ?? o.attr["opf:Scheme"] ?? o.attr.scheme)?.trim();
|
|
80
|
+
e.push({
|
|
81
|
+
value: n,
|
|
82
|
+
...i !== void 0 && i.length > 0 ? { scheme: i } : {}
|
|
83
|
+
});
|
|
84
|
+
}), e;
|
|
85
|
+
}, X = (t) => {
|
|
86
|
+
let e;
|
|
87
|
+
return t.eachChild((o) => {
|
|
88
|
+
if (e !== void 0 || u(o.name).toLowerCase() !== "title") return;
|
|
89
|
+
const n = o.val.trim();
|
|
90
|
+
n.length > 0 && (e = n);
|
|
91
|
+
}), e;
|
|
92
|
+
}, m = (t, e) => {
|
|
93
|
+
const n = p(t, "meta").find(
|
|
94
|
+
(r) => r.attr.property === e
|
|
95
|
+
)?.val;
|
|
96
|
+
if (!(n === void 0 || n.trim().length === 0))
|
|
97
|
+
return n;
|
|
98
|
+
}, B = (t) => {
|
|
99
|
+
const e = d(t, "guide");
|
|
100
|
+
if (e === void 0) return [];
|
|
101
|
+
const o = [];
|
|
102
|
+
for (const n of p(e, "reference")) {
|
|
103
|
+
const r = n.attr.href?.trim();
|
|
104
|
+
r === void 0 || r.length === 0 || o.push({
|
|
105
|
+
href: r,
|
|
106
|
+
title: n.attr.title?.trim() ?? "",
|
|
107
|
+
type: n.attr.type?.trim() ?? ""
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return o;
|
|
111
|
+
}, K = (t) => {
|
|
112
|
+
const e = t.attr.id, o = t.attr.href;
|
|
113
|
+
if (e === void 0 || e.length === 0 || o === void 0 || o.length === 0) return;
|
|
114
|
+
const n = t.attr["media-type"], r = t.attr.properties?.trim();
|
|
115
|
+
return {
|
|
116
|
+
id: e,
|
|
117
|
+
href: o,
|
|
118
|
+
...n !== void 0 && n.length > 0 ? { mediaType: n } : {},
|
|
119
|
+
...r !== void 0 && r.length > 0 ? { properties: r } : {}
|
|
120
|
+
};
|
|
121
|
+
}, G = (t) => {
|
|
122
|
+
const e = [], o = /* @__PURE__ */ new Map();
|
|
123
|
+
for (const n of p(t, "item")) {
|
|
124
|
+
const r = K(n);
|
|
125
|
+
r !== void 0 && (e.push(r), o.set(r.id, r));
|
|
126
|
+
}
|
|
127
|
+
return { items: e, byId: o };
|
|
128
|
+
}, Y = (t, e) => {
|
|
129
|
+
const o = [];
|
|
130
|
+
for (const n of p(e, "itemref")) {
|
|
131
|
+
const r = n.attr.idref;
|
|
132
|
+
if (r === void 0 || r.trim().length === 0) continue;
|
|
133
|
+
const i = t.get(r);
|
|
134
|
+
if (i === void 0) continue;
|
|
135
|
+
const s = x(n.attr.properties);
|
|
136
|
+
o.push({
|
|
137
|
+
idref: r,
|
|
138
|
+
id: i.id,
|
|
139
|
+
href: i.href,
|
|
140
|
+
...i.mediaType !== void 0 ? { mediaType: i.mediaType } : {},
|
|
141
|
+
...i.properties !== void 0 ? { properties: i.properties } : {},
|
|
142
|
+
...s.renditionLayout !== void 0 ? { renditionLayout: s.renditionLayout } : {},
|
|
143
|
+
...s.pageSpreadLeft !== void 0 ? { pageSpreadLeft: s.pageSpreadLeft } : {},
|
|
144
|
+
...s.pageSpreadRight !== void 0 ? { pageSpreadRight: s.pageSpreadRight } : {}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return o;
|
|
148
|
+
}, re = (t) => {
|
|
149
|
+
const e = new l(t), o = d(e, "manifest"), n = d(e, "spine"), r = d(e, "metadata");
|
|
150
|
+
let i = [], s = [];
|
|
151
|
+
if (o !== void 0) {
|
|
152
|
+
const { items: b, byId: O } = G(o);
|
|
153
|
+
i = b, n !== void 0 && (s = Y(O, n));
|
|
154
|
+
}
|
|
155
|
+
const a = n?.attr["page-progression-direction"], E = a !== void 0 && a.trim().length > 0 ? a : void 0, f = n?.attr.toc, S = f !== void 0 && f.trim().length > 0 ? f.trim() : void 0;
|
|
156
|
+
let v, g, h, y;
|
|
157
|
+
const L = [];
|
|
158
|
+
r !== void 0 && (v = X(r), g = m(r, "rendition:layout"), h = m(r, "rendition:flow"), y = m(r, "rendition:spread"), L.push(...R(r)));
|
|
159
|
+
const N = B(e);
|
|
160
|
+
return {
|
|
161
|
+
kind: "opf",
|
|
162
|
+
manifestItems: i,
|
|
163
|
+
spineRows: s,
|
|
164
|
+
spineTocIdref: S,
|
|
165
|
+
identifiers: L,
|
|
166
|
+
title: v,
|
|
167
|
+
renditionLayoutMeta: g,
|
|
168
|
+
renditionFlowMeta: h,
|
|
169
|
+
renditionSpreadMeta: y,
|
|
170
|
+
pageProgressionDirection: E,
|
|
171
|
+
guide: N
|
|
172
|
+
};
|
|
173
|
+
}, U = (t) => {
|
|
174
|
+
const e = t.displayOptions?.platform?.options?.find(
|
|
175
|
+
(n) => n.name === "fixed-layout"
|
|
176
|
+
)?.value;
|
|
177
|
+
if (e === void 0) return {};
|
|
178
|
+
const o = e.trim().toLowerCase() === "true" ? "pre-paginated" : void 0;
|
|
179
|
+
return o !== void 0 ? { renditionLayout: o } : {};
|
|
180
|
+
}, z = /* @__PURE__ */ new Set([8, 12, 13, 14]), C = (t) => {
|
|
181
|
+
if (t == null) return;
|
|
182
|
+
const e = String(t).replace(/\D/g, "");
|
|
183
|
+
if (!(e.length === 0 || !z.has(e.length)))
|
|
184
|
+
return e;
|
|
185
|
+
}, H = /(?:97[89])?\d{9}[\dXx]/, c = (t) => {
|
|
186
|
+
if (t == null) return;
|
|
187
|
+
const e = String(t).trim().replace(/^urn:isbn:/i, "").replace(/^isbn[:\s-]*/i, ""), o = e.replace(/[^0-9Xx]/g, "");
|
|
188
|
+
if (o.length === 10 || o.length === 13)
|
|
189
|
+
return o.toUpperCase();
|
|
190
|
+
const n = e.match(H);
|
|
191
|
+
if (n) return n[0].toUpperCase();
|
|
192
|
+
}, V = (t) => t.Manga === "YesAndRightToLeft" ? "rtl" : "ltr", $ = (t) => {
|
|
193
|
+
const e = t.GTIN, o = C(e), n = c(e);
|
|
194
|
+
return {
|
|
195
|
+
...o !== void 0 ? { gtin: o } : {},
|
|
196
|
+
...n !== void 0 ? { isbn: n } : {},
|
|
197
|
+
readingDirection: V(t)
|
|
198
|
+
};
|
|
199
|
+
}, q = (t) => {
|
|
200
|
+
const { renditionLayout: e } = t;
|
|
201
|
+
return e !== void 0 ? { renditionLayout: e } : {};
|
|
202
|
+
}, j = (t) => {
|
|
203
|
+
for (const e of t)
|
|
204
|
+
if (e.scheme !== void 0 && e.scheme.trim().toLowerCase() === "isbn" && c(e.value) !== void 0)
|
|
205
|
+
return e.value;
|
|
206
|
+
for (const e of t)
|
|
207
|
+
if (c(e.value) !== void 0) return e.value;
|
|
208
|
+
}, J = (t) => {
|
|
209
|
+
const e = t.pageProgressionDirection?.trim().toLowerCase(), o = e === "ltr" || e === "rtl" ? e : void 0, n = t.renditionLayoutMeta?.trim().toLowerCase(), r = n === "reflowable" || n === "pre-paginated" ? n : void 0, i = j(t.identifiers), s = c(i), a = C(i);
|
|
210
|
+
return {
|
|
211
|
+
...a !== void 0 ? { gtin: a } : {},
|
|
212
|
+
...s !== void 0 ? { isbn: s } : {},
|
|
213
|
+
...o !== void 0 ? { readingDirection: o } : {},
|
|
214
|
+
...r !== void 0 ? { renditionLayout: r } : {}
|
|
215
|
+
};
|
|
216
|
+
}, ie = (t) => t.kind === "comicInfo" ? $(t) : t.kind === "kobo" ? q(t) : t.kind === "apple" ? U(t) : J(t);
|
|
217
|
+
export {
|
|
218
|
+
W as APPLE_IBOOKS_DISPLAY_OPTIONS_FILENAME,
|
|
219
|
+
T as COMIC_INFO_FILENAME,
|
|
220
|
+
A as COMIC_INFO_MANGA_VALUES,
|
|
221
|
+
oe as KOBO_DISPLAY_OPTIONS_FILENAME,
|
|
222
|
+
ee as isComicInfoManga,
|
|
223
|
+
C as normalizeGtin,
|
|
224
|
+
c as normalizeIsbn,
|
|
225
|
+
Z as parseAppleDisplayOptionsXml,
|
|
226
|
+
te as parseComicInfo,
|
|
227
|
+
ne as parseKoboXml,
|
|
228
|
+
re as parseOpf,
|
|
229
|
+
ie as resolveArchiveMetadata,
|
|
230
|
+
F as tokenizeXmlSpaceSeparatedList
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/apple/parse.ts","../src/comicInfo/manga.ts","../src/comicInfo/parse.ts","../src/kobo/parse.ts","../src/utils/tokenizeXmlSpaceSeparatedList.ts","../src/opf/spineItemrefProperties.ts","../src/opf/parse.ts","../src/apple/resolve.ts","../src/utils/normalizeGtin.ts","../src/utils/normalizeIsbn.ts","../src/comicInfo/resolve.ts","../src/kobo/resolve.ts","../src/opf/resolve.ts","../src/resolve.ts"],"sourcesContent":["import type { XmlElement } from \"xmldoc\"\nimport { XmlDocument } from \"xmldoc\"\n\nexport const APPLE_IBOOKS_DISPLAY_OPTIONS_FILENAME =\n \"com.apple.ibooks.display-options.xml\"\n\nexport type AppleDisplayOption = {\n readonly name?: string\n readonly value: string\n}\n\nexport type AppleMetadata = {\n readonly kind: \"apple\"\n readonly displayOptions?: {\n readonly platform?: {\n readonly options: ReadonlyArray<AppleDisplayOption>\n }\n }\n}\n\nconst platformOptionsFromElement = (\n platform: XmlElement,\n): ReadonlyArray<AppleDisplayOption> =>\n platform.childrenNamed(\"option\").map((option) => ({\n name: option.attr?.name,\n value: option.val,\n }))\n\nexport const parseAppleDisplayOptionsXml = (xml: string): AppleMetadata => {\n const doc = new XmlDocument(xml)\n const root = doc.name?.toLowerCase()\n\n if (root !== \"display_options\") {\n return { kind: \"apple\" }\n }\n\n const platformEl = doc.childNamed(\"platform\")\n\n return {\n kind: \"apple\",\n displayOptions: {\n ...(platformEl !== undefined\n ? { platform: { options: platformOptionsFromElement(platformEl) } }\n : {}),\n },\n }\n}\n","/**\n * Allowed `Manga` element values (ComicInfo 2.0 `Manga` simpleType).\n *\n * @see https://anansi-project.github.io/docs/comicinfo/documentation#manga\n */\nexport const COMIC_INFO_MANGA_VALUES = [\n \"Unknown\",\n \"No\",\n \"Yes\",\n \"YesAndRightToLeft\",\n] as const\n\nexport type ComicInfoManga = (typeof COMIC_INFO_MANGA_VALUES)[number]\n\nexport const isComicInfoManga = (value: string): value is ComicInfoManga => {\n for (const v of COMIC_INFO_MANGA_VALUES) {\n if (v === value) return true\n }\n return false\n}\n","import type { XmlElement } from \"xmldoc\"\nimport { XmlDocument } from \"xmldoc\"\nimport type { ComicInfoManga } from \"./manga\"\n\n/** Canonical top-level filename; real archives may use any casing. */\nexport const COMIC_INFO_FILENAME = \"ComicInfo.xml\"\n\n/**\n * Parsed `ComicInfo.xml` root: one optional string property per child element,\n * using the same names as in the file (e.g. `Title`, `GTIN`, `LanguageISO`).\n * Nested blocks such as `Pages` are skipped. Other simple child elements are\n * still copied onto this object under their tag name.\n *\n * @see https://anansi-project.github.io/docs/comicinfo/intro\n * @see https://github.com/anansi-project/comicinfo/blob/main/drafts/v2.1/ComicInfo.xsd for schema\n */\nexport interface ComicInfo {\n readonly kind: \"comicInfo\"\n AgeRating?: string\n AlternateCount?: string\n AlternateNumber?: string\n AlternateSeries?: string\n BlackAndWhite?: string\n Characters?: string\n Colorist?: string\n CommunityRating?: string\n Count?: string\n CoverArtist?: string\n Day?: string\n Editor?: string\n Format?: string\n Genre?: string\n GTIN?: string\n Imprint?: string\n Inker?: string\n LanguageISO?: string\n Letterer?: string\n Locations?: string\n MainCharacterOrTeam?: string\n /** Schema literals per {@link ComicInfoManga}; files may still use other strings. */\n Manga?: ComicInfoManga | (string & {})\n Month?: string\n Notes?: string\n Number?: string\n PageCount?: string\n Penciller?: string\n Publisher?: string\n Review?: string\n ScanInformation?: string\n Series?: string\n SeriesGroup?: string\n StoryArc?: string\n StoryArcNumber?: string\n Summary?: string\n Tags?: string\n Teams?: string\n Title?: string\n Translator?: string\n Volume?: string\n Web?: string\n Writer?: string\n Year?: string\n [tag: string]: string | undefined\n}\n\nconst SKIP_ELEMENT_CHILDREN = new Set([\"Pages\", \"ComicInfo\"])\n\nconst hasNestedElement = (el: XmlElement) =>\n el.children.some((c) => c.type === \"element\")\n\nconst trimmedText = (el: XmlElement): string | undefined => {\n const t = el.val.trim()\n return t.length > 0 ? t : undefined\n}\n\n/**\n * Parse a raw `ComicInfo.xml` body. Each direct child element with plain text\n * becomes a property named after that tag. Malformed XML throws; the parser\n * error is attached as `cause`.\n */\nexport const parseComicInfo = (xml: string): ComicInfo => {\n let doc: XmlDocument\n try {\n doc = new XmlDocument(xml)\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause)\n throw new Error(`${COMIC_INFO_FILENAME} is malformed: ${message}`, {\n cause,\n })\n }\n\n const fields: Record<string, string> = {}\n\n doc.eachChild((child) => {\n if (child.type !== \"element\") return\n if (SKIP_ELEMENT_CHILDREN.has(child.name)) return\n if (hasNestedElement(child)) return\n\n const text = trimmedText(child)\n if (text === undefined) return\n if (fields[child.name] !== undefined) return\n\n fields[child.name] = text\n })\n\n // `as ComicInfo`: TS cannot infer that spreading `Record<string, string>` into `{ kind }` satisfies the named optional fields plus `[tag: string]: string | undefined` on `ComicInfo`.\n return { kind: \"comicInfo\", ...fields } as ComicInfo\n}\n","import { XmlDocument } from \"xmldoc\"\n\nexport const KOBO_DISPLAY_OPTIONS_FILENAME = \"com.kobobooks.display-options.xml\"\n\n/**\n * Normalized fields from Kobo-specific XML sidecars. Grows as new Kobo\n * document kinds are supported; absent keys mean not present or unknown.\n */\nexport type KoboMetadata = {\n readonly kind: \"kobo\"\n renditionLayout?: `reflowable` | `pre-paginated`\n}\n\nconst parseKoboDisplayOptionsDocument = (\n doc: XmlDocument,\n): { renditionLayout?: \"pre-paginated\" } => {\n const platform = doc.childNamed(\"platform\")\n if (!platform) return {}\n\n for (const option of platform.childrenNamed(\"option\")) {\n if (option.attr?.name !== \"fixed-layout\") continue\n if (option.val.trim().toLowerCase() === \"true\") {\n return { renditionLayout: \"pre-paginated\" }\n }\n return {}\n }\n\n return {}\n}\n\n/**\n * Parse a Kobo-related XML document. Unsupported or unrecognized roots\n * yield an empty object. Malformed XML throws from the underlying parser.\n */\nexport const parseKoboXml = (xml: string): KoboMetadata => {\n const doc = new XmlDocument(xml)\n const root = doc.name?.toLowerCase()\n\n if (root === \"display_options\") {\n return { kind: \"kobo\", ...parseKoboDisplayOptionsDocument(doc) }\n }\n\n return { kind: \"kobo\" }\n}\n","/**\n * EPUB/XML space-separated token lists (e.g. `properties` on `item` / `itemref`).\n * Trims the raw string, splits on ASCII whitespace, drops empty segments.\n */\nexport const tokenizeXmlSpaceSeparatedList = (\n raw: string | undefined,\n): readonly string[] => {\n if (raw === undefined || raw.trim().length === 0) {\n return []\n }\n\n return raw\n .trim()\n .split(/\\s+/)\n .filter((t) => t.length > 0)\n}\n","import { tokenizeXmlSpaceSeparatedList } from \"../utils/tokenizeXmlSpaceSeparatedList\"\n\nexport type OpfItemrefLayoutHints = {\n readonly renditionLayout?: `reflowable` | `pre-paginated`\n readonly pageSpreadLeft?: true\n readonly pageSpreadRight?: true\n}\n\n/**\n * EPUB `itemref` `properties` attribute (space-separated tokens).\n *\n * @see https://www.w3.org/TR/epub/#attrdef-properties\n */\nexport const layoutHintsFromItemrefProperties = (\n properties: string | undefined,\n): OpfItemrefLayoutHints => {\n const tokens = tokenizeXmlSpaceSeparatedList(properties)\n if (tokens.length === 0) {\n return {}\n }\n\n let renditionLayout: `reflowable` | `pre-paginated` | undefined\n if (tokens.includes(`rendition:layout-reflowable`)) {\n renditionLayout = `reflowable`\n }\n if (tokens.includes(`rendition:layout-pre-paginated`)) {\n renditionLayout = `pre-paginated`\n }\n\n return {\n ...(renditionLayout !== undefined ? { renditionLayout } : {}),\n ...(tokens.includes(`page-spread-left`)\n ? { pageSpreadLeft: true as const }\n : {}),\n ...(tokens.includes(`page-spread-right`)\n ? { pageSpreadRight: true as const }\n : {}),\n }\n}\n","import type { XmlElement, XmlNodeBase } from \"xmldoc\"\nimport { XmlDocument } from \"xmldoc\"\nimport { layoutHintsFromItemrefProperties } from \"./spineItemrefProperties\"\n\nexport type OpfSpineManifestItem = {\n readonly id: string\n readonly href: string\n readonly mediaType?: string\n readonly properties?: string\n}\n\nexport type OpfIdentifier = {\n readonly value: string\n readonly scheme?: string\n}\n\nexport type OpfSpineRow = {\n readonly idref: string\n readonly id: string\n readonly href: string\n readonly mediaType?: string\n readonly properties?: string\n readonly renditionLayout?: `reflowable` | `pre-paginated`\n readonly pageSpreadLeft?: true\n readonly pageSpreadRight?: true\n}\n\nexport type OpfGuideReference = {\n readonly href: string\n readonly title: string\n readonly type: string\n}\n\nconst elementLocalName = (name: string): string =>\n name.includes(\":\") ? name.slice(name.lastIndexOf(\":\") + 1) : name\n\nconst localNameEq = (elementName: string, wantLocal: string): boolean =>\n elementLocalName(elementName).toLowerCase() === wantLocal.toLowerCase()\n\nconst isXmlElement = (node: XmlNodeBase): node is XmlElement =>\n node.type === \"element\"\n\nconst childNamedLocal = (\n parent: XmlElement,\n localName: string,\n): XmlElement | undefined => {\n for (const node of parent.children) {\n if (!isXmlElement(node)) continue\n if (localNameEq(node.name, localName)) return node\n }\n return undefined\n}\n\nconst childrenNamedLocal = (\n parent: XmlElement,\n localName: string,\n): XmlElement[] => {\n const out: XmlElement[] = []\n for (const node of parent.children) {\n if (!isXmlElement(node)) continue\n if (localNameEq(node.name, localName)) out.push(node)\n }\n return out\n}\n\nconst identifiersFromMetadata = (metadataEl: XmlElement): OpfIdentifier[] => {\n const identifiers: OpfIdentifier[] = []\n\n metadataEl.eachChild((child) => {\n if (elementLocalName(child.name).toLowerCase() !== \"identifier\") return\n\n const value = child.val.trim()\n if (value.length === 0) return\n\n const scheme =\n child.attr[\"opf:scheme\"] ?? child.attr[\"opf:Scheme\"] ?? child.attr.scheme\n const schemeTrimmed = scheme?.trim()\n\n identifiers.push({\n value,\n ...(schemeTrimmed !== undefined && schemeTrimmed.length > 0\n ? { scheme: schemeTrimmed }\n : {}),\n })\n })\n\n return identifiers\n}\n\nconst titleFromMetadata = (metadataEl: XmlElement): string | undefined => {\n let found: string | undefined\n metadataEl.eachChild((child) => {\n if (found !== undefined) return\n if (elementLocalName(child.name).toLowerCase() !== \"title\") return\n const t = child.val.trim()\n if (t.length > 0) found = t\n })\n return found\n}\n\nconst metaValByProperty = (\n metadataEl: XmlElement,\n property: string,\n): string | undefined => {\n const meta = childrenNamedLocal(metadataEl, \"meta\").find(\n (m) => m.attr.property === property,\n )\n const raw = meta?.val\n if (raw === undefined || raw.trim().length === 0) return undefined\n return raw\n}\n\nconst guideFromPackage = (doc: XmlElement): OpfGuideReference[] => {\n const guideEl = childNamedLocal(doc, \"guide\")\n if (guideEl === undefined) return []\n\n const refs: OpfGuideReference[] = []\n\n for (const ref of childrenNamedLocal(guideEl, \"reference\")) {\n const href = ref.attr.href?.trim()\n if (href === undefined || href.length === 0) continue\n refs.push({\n href,\n title: ref.attr.title?.trim() ?? ``,\n type: ref.attr.type?.trim() ?? ``,\n })\n }\n\n return refs\n}\n\nconst manifestItemFromXmlElement = (\n item: XmlElement,\n): OpfSpineManifestItem | undefined => {\n const id = item.attr.id\n const href = item.attr.href\n if (id === undefined || id.length === 0) return undefined\n if (href === undefined || href.length === 0) return undefined\n\n const mediaType = item.attr[\"media-type\"]\n const properties = item.attr.properties?.trim()\n return {\n id,\n href,\n ...(mediaType !== undefined && mediaType.length > 0 ? { mediaType } : {}),\n ...(properties !== undefined && properties.length > 0\n ? { properties }\n : {}),\n }\n}\n\nconst manifestItemsAndById = (\n manifestEl: XmlElement,\n): {\n items: OpfSpineManifestItem[]\n byId: Map<string, OpfSpineManifestItem>\n} => {\n const items: OpfSpineManifestItem[] = []\n const byId = new Map<string, OpfSpineManifestItem>()\n\n for (const el of childrenNamedLocal(manifestEl, \"item\")) {\n const parsed = manifestItemFromXmlElement(el)\n if (parsed === undefined) continue\n items.push(parsed)\n byId.set(parsed.id, parsed)\n }\n\n return { items, byId }\n}\n\nconst spineRowsFromByIdAndSpine = (\n byId: Map<string, OpfSpineManifestItem>,\n spineEl: XmlElement,\n): OpfSpineRow[] => {\n const rows: OpfSpineRow[] = []\n\n for (const itemref of childrenNamedLocal(spineEl, \"itemref\")) {\n const idref = itemref.attr.idref\n if (idref === undefined || idref.trim().length === 0) continue\n\n const manifestItem = byId.get(idref)\n if (manifestItem === undefined) continue\n\n const hints = layoutHintsFromItemrefProperties(itemref.attr.properties)\n\n rows.push({\n idref,\n id: manifestItem.id,\n href: manifestItem.href,\n ...(manifestItem.mediaType !== undefined\n ? { mediaType: manifestItem.mediaType }\n : {}),\n ...(manifestItem.properties !== undefined\n ? { properties: manifestItem.properties }\n : {}),\n ...(hints.renditionLayout !== undefined\n ? { renditionLayout: hints.renditionLayout }\n : {}),\n ...(hints.pageSpreadLeft !== undefined\n ? { pageSpreadLeft: hints.pageSpreadLeft }\n : {}),\n ...(hints.pageSpreadRight !== undefined\n ? { pageSpreadRight: hints.pageSpreadRight }\n : {}),\n })\n }\n\n return rows\n}\n\nexport type OpfMetadata = {\n readonly kind: \"opf\"\n readonly manifestItems: ReadonlyArray<OpfSpineManifestItem>\n readonly spineRows: ReadonlyArray<OpfSpineRow>\n readonly spineTocIdref: string | undefined\n readonly identifiers: ReadonlyArray<OpfIdentifier>\n readonly title: string | undefined\n readonly renditionLayoutMeta: string | undefined\n readonly renditionFlowMeta: string | undefined\n readonly renditionSpreadMeta: string | undefined\n readonly pageProgressionDirection: string | undefined\n readonly guide: ReadonlyArray<OpfGuideReference>\n}\n\n/**\n * Parses an EPUB package document (OPF) into structured metadata.\n *\n * Direct children of `package` (`metadata`, `manifest`, `spine`, `guide`) and\n * their structural children (`item`, `itemref`, `reference`, `meta`) are\n * matched by **local name** (ASCII case-insensitive), so prefixed tags such as\n * `opf:manifest` are supported the same as unprefixed `manifest`.\n *\n * Attribute names on `spine` / `itemref` are still read as emitted by xmldoc\n * (no QName normalization).\n */\nexport const parseOpf = (opfXml: string): OpfMetadata => {\n const doc = new XmlDocument(opfXml)\n const manifestEl = childNamedLocal(doc, \"manifest\")\n const spineEl = childNamedLocal(doc, \"spine\")\n const metadataEl = childNamedLocal(doc, \"metadata\")\n\n let manifestItems: OpfSpineManifestItem[] = []\n let spineRows: OpfSpineRow[] = []\n\n if (manifestEl !== undefined) {\n const { items, byId } = manifestItemsAndById(manifestEl)\n manifestItems = items\n if (spineEl !== undefined) {\n spineRows = spineRowsFromByIdAndSpine(byId, spineEl)\n }\n }\n\n const pageProgressionDirectionRaw =\n spineEl?.attr[\"page-progression-direction\"]\n const pageProgressionDirection =\n pageProgressionDirectionRaw !== undefined &&\n pageProgressionDirectionRaw.trim().length > 0\n ? pageProgressionDirectionRaw\n : undefined\n\n const spineTocRaw = spineEl?.attr.toc\n const spineTocIdref =\n spineTocRaw !== undefined && spineTocRaw.trim().length > 0\n ? spineTocRaw.trim()\n : undefined\n\n let title: string | undefined\n let renditionLayoutMeta: string | undefined\n let renditionFlowMeta: string | undefined\n let renditionSpreadMeta: string | undefined\n const identifiers: OpfIdentifier[] = []\n\n if (metadataEl !== undefined) {\n title = titleFromMetadata(metadataEl)\n renditionLayoutMeta = metaValByProperty(metadataEl, \"rendition:layout\")\n renditionFlowMeta = metaValByProperty(metadataEl, \"rendition:flow\")\n renditionSpreadMeta = metaValByProperty(metadataEl, \"rendition:spread\")\n identifiers.push(...identifiersFromMetadata(metadataEl))\n }\n\n const guide = guideFromPackage(doc)\n\n return {\n kind: \"opf\",\n manifestItems,\n spineRows,\n spineTocIdref,\n identifiers,\n title,\n renditionLayoutMeta,\n renditionFlowMeta,\n renditionSpreadMeta,\n pageProgressionDirection,\n guide,\n }\n}\n","import type { ArchiveResolveResult } from \"../types/archiveResolve\"\nimport type { AppleMetadata } from \"./parse\"\n\nexport const resolveApple = (input: AppleMetadata): ArchiveResolveResult => {\n const fixedLayout = input.displayOptions?.platform?.options?.find(\n (o) => o.name === \"fixed-layout\",\n )?.value\n\n if (fixedLayout === undefined) return {}\n\n const renditionLayout =\n fixedLayout.trim().toLowerCase() === \"true\"\n ? (\"pre-paginated\" as const)\n : undefined\n\n return renditionLayout !== undefined ? { renditionLayout } : {}\n}\n","const GTIN_LENGTHS = new Set([8, 12, 13, 14])\n\n/**\n * Normalize a raw GTIN / EAN / UPC string to digits only when the length\n * matches a GS1 GTIN family size (8, 12, 13, or 14). Does not verify check digits.\n */\nexport const normalizeGtin = (\n raw: string | number | undefined | null,\n): string | undefined => {\n if (raw === undefined || raw === null) return undefined\n\n const digits = String(raw).replace(/\\D/g, \"\")\n if (digits.length === 0 || !GTIN_LENGTHS.has(digits.length)) return undefined\n\n return digits\n}\n","const ISBN_CANDIDATE_PATTERN = /(?:97[89])?\\d{9}[\\dXx]/\n\n/**\n * Normalize a raw ISBN-ish string into a canonical 10- or 13-character\n * form, or `undefined` when no recognisable ISBN can be recovered.\n *\n * - Strips the common `urn:isbn:` / `isbn:` prefixes.\n * - Drops everything that isn't a digit or `X`.\n * - Validates the resulting length (10 or 13).\n * - Falls back to a lax regex scan so publishers that stuff free text\n * around the number still yield a usable value.\n */\nexport const normalizeIsbn = (\n raw: string | number | undefined | null,\n): string | undefined => {\n if (raw === undefined || raw === null) return undefined\n\n const stripped = String(raw)\n .trim()\n .replace(/^urn:isbn:/i, \"\")\n .replace(/^isbn[:\\s-]*/i, \"\")\n\n const digitsOnly = stripped.replace(/[^0-9Xx]/g, \"\")\n\n if (digitsOnly.length === 10 || digitsOnly.length === 13) {\n return digitsOnly.toUpperCase()\n }\n\n const match = stripped.match(ISBN_CANDIDATE_PATTERN)\n if (match) return match[0].toUpperCase()\n\n return undefined\n}\n","import type { ArchiveResolveResult } from \"../types/archiveResolve\"\nimport { normalizeGtin } from \"../utils/normalizeGtin\"\nimport { normalizeIsbn } from \"../utils/normalizeIsbn\"\nimport type { ComicInfo } from \"./parse\"\n\nconst readingDirection = (info: ComicInfo): \"ltr\" | \"rtl\" =>\n info.Manga === \"YesAndRightToLeft\" ? \"rtl\" : \"ltr\"\n\nexport const resolveComicInfo = (info: ComicInfo): ArchiveResolveResult => {\n const raw = info.GTIN\n const gtin = normalizeGtin(raw)\n const isbn = normalizeIsbn(raw)\n return {\n ...(gtin !== undefined ? { gtin } : {}),\n ...(isbn !== undefined ? { isbn } : {}),\n readingDirection: readingDirection(info),\n }\n}\n","import type { ArchiveResolveResult } from \"../types/archiveResolve\"\nimport type { KoboMetadata } from \"./parse\"\n\nexport const resolveKobo = (input: KoboMetadata): ArchiveResolveResult => {\n const { renditionLayout } = input\n return renditionLayout !== undefined ? { renditionLayout } : {}\n}\n","import type { ArchiveResolveResult } from \"../types/archiveResolve\"\nimport { normalizeGtin } from \"../utils/normalizeGtin\"\nimport { normalizeIsbn } from \"../utils/normalizeIsbn\"\nimport type { OpfIdentifier, OpfMetadata } from \"./parse\"\n\nconst rawIdentifierValueForIsbn = (\n identifiers: ReadonlyArray<OpfIdentifier>,\n): string | undefined => {\n for (const i of identifiers) {\n if (i.scheme !== undefined && i.scheme.trim().toLowerCase() === \"isbn\") {\n if (normalizeIsbn(i.value) !== undefined) return i.value\n }\n }\n\n for (const i of identifiers) {\n if (normalizeIsbn(i.value) !== undefined) return i.value\n }\n\n return undefined\n}\n\nexport const resolveOpf = (input: OpfMetadata): ArchiveResolveResult => {\n const ppd = input.pageProgressionDirection?.trim().toLowerCase()\n const readingDirection = ppd === \"ltr\" || ppd === \"rtl\" ? ppd : undefined\n\n const rl = input.renditionLayoutMeta?.trim().toLowerCase()\n const renditionLayout =\n rl === \"reflowable\" || rl === \"pre-paginated\" ? rl : undefined\n\n const raw = rawIdentifierValueForIsbn(input.identifiers)\n const isbn = normalizeIsbn(raw)\n const gtin = normalizeGtin(raw)\n\n return {\n ...(gtin !== undefined ? { gtin } : {}),\n ...(isbn !== undefined ? { isbn } : {}),\n ...(readingDirection !== undefined ? { readingDirection } : {}),\n ...(renditionLayout !== undefined ? { renditionLayout } : {}),\n }\n}\n","import type { AppleMetadata } from \"./apple/parse\"\nimport { resolveApple } from \"./apple/resolve\"\nimport type { ComicInfo } from \"./comicInfo/parse\"\nimport { resolveComicInfo } from \"./comicInfo/resolve\"\nimport type { KoboMetadata } from \"./kobo/parse\"\nimport { resolveKobo } from \"./kobo/resolve\"\nimport type { OpfMetadata } from \"./opf/parse\"\nimport { resolveOpf } from \"./opf/resolve\"\nimport type { ArchiveResolveResult } from \"./types/archiveResolve\"\n\nexport type ResolvedArchiveInput =\n | ComicInfo\n | KoboMetadata\n | AppleMetadata\n | OpfMetadata\n\nexport const resolveArchiveMetadata = (\n input: ResolvedArchiveInput,\n): ArchiveResolveResult => {\n if (input.kind === \"comicInfo\") return resolveComicInfo(input)\n if (input.kind === \"kobo\") return resolveKobo(input)\n if (input.kind === \"apple\") return resolveApple(input)\n return resolveOpf(input)\n}\n"],"names":["APPLE_IBOOKS_DISPLAY_OPTIONS_FILENAME","platformOptionsFromElement","platform","option","parseAppleDisplayOptionsXml","xml","doc","XmlDocument","platformEl","COMIC_INFO_MANGA_VALUES","isComicInfoManga","value","v","COMIC_INFO_FILENAME","SKIP_ELEMENT_CHILDREN","hasNestedElement","el","c","trimmedText","t","parseComicInfo","cause","message","fields","child","text","KOBO_DISPLAY_OPTIONS_FILENAME","parseKoboDisplayOptionsDocument","parseKoboXml","tokenizeXmlSpaceSeparatedList","raw","layoutHintsFromItemrefProperties","properties","tokens","renditionLayout","elementLocalName","name","localNameEq","elementName","wantLocal","isXmlElement","node","childNamedLocal","parent","localName","childrenNamedLocal","out","identifiersFromMetadata","metadataEl","identifiers","schemeTrimmed","titleFromMetadata","found","metaValByProperty","property","m","guideFromPackage","guideEl","refs","ref","href","manifestItemFromXmlElement","item","id","mediaType","manifestItemsAndById","manifestEl","items","byId","parsed","spineRowsFromByIdAndSpine","spineEl","rows","itemref","idref","manifestItem","hints","parseOpf","opfXml","manifestItems","spineRows","pageProgressionDirectionRaw","pageProgressionDirection","spineTocRaw","spineTocIdref","title","renditionLayoutMeta","renditionFlowMeta","renditionSpreadMeta","guide","resolveApple","input","fixedLayout","o","GTIN_LENGTHS","normalizeGtin","digits","ISBN_CANDIDATE_PATTERN","normalizeIsbn","stripped","digitsOnly","match","readingDirection","info","resolveComicInfo","gtin","isbn","resolveKobo","rawIdentifierValueForIsbn","i","resolveOpf","ppd","rl","resolveArchiveMetadata"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(r,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("xmldoc")):typeof define=="function"&&define.amd?define(["exports","xmldoc"],d):(r=typeof globalThis<"u"?globalThis:r||self,d(r["prose-reader-archive-parser"]={},r.xmldoc))})(this,(function(r,d){"use strict";const A="com.apple.ibooks.display-options.xml",w=t=>t.childrenNamed("option").map(e=>({name:e.attr?.name,value:e.val})),M=t=>{const e=new d.XmlDocument(t);if(e.name?.toLowerCase()!=="display_options")return{kind:"apple"};const n=e.childNamed("platform");return{kind:"apple",displayOptions:{...n!==void 0?{platform:{options:w(n)}}:{}}}},h=["Unknown","No","Yes","YesAndRightToLeft"],b=t=>{for(const e of h)if(e===t)return!0;return!1},I="ComicInfo.xml",T=new Set(["Pages","ComicInfo"]),D=t=>t.children.some(e=>e.type==="element"),P=t=>{const e=t.val.trim();return e.length>0?e:void 0},k=t=>{let e;try{e=new d.XmlDocument(t)}catch(n){const i=n instanceof Error?n.message:String(n);throw new Error(`${I} is malformed: ${i}`,{cause:n})}const o={};return e.eachChild(n=>{if(n.type!=="element"||T.has(n.name)||D(n))return;const i=P(n);i!==void 0&&o[n.name]===void 0&&(o[n.name]=i)}),{kind:"comicInfo",...o}},F="com.kobobooks.display-options.xml",X=t=>{const e=t.childNamed("platform");if(!e)return{};for(const o of e.childrenNamed("option"))if(o.attr?.name==="fixed-layout")return o.val.trim().toLowerCase()==="true"?{renditionLayout:"pre-paginated"}:{};return{}},R=t=>{const e=new d.XmlDocument(t);return e.name?.toLowerCase()==="display_options"?{kind:"kobo",...X(e)}:{kind:"kobo"}},L=t=>t===void 0||t.trim().length===0?[]:t.trim().split(/\s+/).filter(e=>e.length>0),K=t=>{const e=L(t);if(e.length===0)return{};let o;return e.includes("rendition:layout-reflowable")&&(o="reflowable"),e.includes("rendition:layout-pre-paginated")&&(o="pre-paginated"),{...o!==void 0?{renditionLayout:o}:{},...e.includes("page-spread-left")?{pageSpreadLeft:!0}:{},...e.includes("page-spread-right")?{pageSpreadRight:!0}:{}}},p=t=>t.includes(":")?t.slice(t.lastIndexOf(":")+1):t,y=(t,e)=>p(t).toLowerCase()===e.toLowerCase(),S=t=>t.type==="element",f=(t,e)=>{for(const o of t.children)if(S(o)&&y(o.name,e))return o},m=(t,e)=>{const o=[];for(const n of t.children)S(n)&&y(n.name,e)&&o.push(n);return o},B=t=>{const e=[];return t.eachChild(o=>{if(p(o.name).toLowerCase()!=="identifier")return;const n=o.val.trim();if(n.length===0)return;const s=(o.attr["opf:scheme"]??o.attr["opf:Scheme"]??o.attr.scheme)?.trim();e.push({value:n,...s!==void 0&&s.length>0?{scheme:s}:{}})}),e},G=t=>{let e;return t.eachChild(o=>{if(e!==void 0||p(o.name).toLowerCase()!=="title")return;const n=o.val.trim();n.length>0&&(e=n)}),e},u=(t,e)=>{const n=m(t,"meta").find(i=>i.attr.property===e)?.val;if(!(n===void 0||n.trim().length===0))return n},Y=t=>{const e=f(t,"guide");if(e===void 0)return[];const o=[];for(const n of m(e,"reference")){const i=n.attr.href?.trim();i===void 0||i.length===0||o.push({href:i,title:n.attr.title?.trim()??"",type:n.attr.type?.trim()??""})}return o},z=t=>{const e=t.attr.id,o=t.attr.href;if(e===void 0||e.length===0||o===void 0||o.length===0)return;const n=t.attr["media-type"],i=t.attr.properties?.trim();return{id:e,href:o,...n!==void 0&&n.length>0?{mediaType:n}:{},...i!==void 0&&i.length>0?{properties:i}:{}}},U=t=>{const e=[],o=new Map;for(const n of m(t,"item")){const i=z(n);i!==void 0&&(e.push(i),o.set(i.id,i))}return{items:e,byId:o}},V=(t,e)=>{const o=[];for(const n of m(e,"itemref")){const i=n.attr.idref;if(i===void 0||i.trim().length===0)continue;const s=t.get(i);if(s===void 0)continue;const a=K(n.attr.properties);o.push({idref:i,id:s.id,href:s.href,...s.mediaType!==void 0?{mediaType:s.mediaType}:{},...s.properties!==void 0?{properties:s.properties}:{},...a.renditionLayout!==void 0?{renditionLayout:a.renditionLayout}:{},...a.pageSpreadLeft!==void 0?{pageSpreadLeft:a.pageSpreadLeft}:{},...a.pageSpreadRight!==void 0?{pageSpreadRight:a.pageSpreadRight}:{}})}return o},H=t=>{const e=new d.XmlDocument(t),o=f(e,"manifest"),n=f(e,"spine"),i=f(e,"metadata");let s=[],a=[];if(o!==void 0){const{items:ie,byId:re}=U(o);s=ie,n!==void 0&&(a=V(re,n))}const c=n?.attr["page-progression-direction"],te=c!==void 0&&c.trim().length>0?c:void 0,g=n?.attr.toc,oe=g!==void 0&&g.trim().length>0?g.trim():void 0;let O,E,N,C;const _=[];i!==void 0&&(O=G(i),E=u(i,"rendition:layout"),N=u(i,"rendition:flow"),C=u(i,"rendition:spread"),_.push(...B(i)));const ne=Y(e);return{kind:"opf",manifestItems:s,spineRows:a,spineTocIdref:oe,identifiers:_,title:O,renditionLayoutMeta:E,renditionFlowMeta:N,renditionSpreadMeta:C,pageProgressionDirection:te,guide:ne}},j=t=>{const e=t.displayOptions?.platform?.options?.find(n=>n.name==="fixed-layout")?.value;if(e===void 0)return{};const o=e.trim().toLowerCase()==="true"?"pre-paginated":void 0;return o!==void 0?{renditionLayout:o}:{}},q=new Set([8,12,13,14]),v=t=>{if(t==null)return;const e=String(t).replace(/\D/g,"");if(!(e.length===0||!q.has(e.length)))return e},$=/(?:97[89])?\d{9}[\dXx]/,l=t=>{if(t==null)return;const e=String(t).trim().replace(/^urn:isbn:/i,"").replace(/^isbn[:\s-]*/i,""),o=e.replace(/[^0-9Xx]/g,"");if(o.length===10||o.length===13)return o.toUpperCase();const n=e.match($);if(n)return n[0].toUpperCase()},x=t=>t.Manga==="YesAndRightToLeft"?"rtl":"ltr",J=t=>{const e=t.GTIN,o=v(e),n=l(e);return{...o!==void 0?{gtin:o}:{},...n!==void 0?{isbn:n}:{},readingDirection:x(t)}},Q=t=>{const{renditionLayout:e}=t;return e!==void 0?{renditionLayout:e}:{}},W=t=>{for(const e of t)if(e.scheme!==void 0&&e.scheme.trim().toLowerCase()==="isbn"&&l(e.value)!==void 0)return e.value;for(const e of t)if(l(e.value)!==void 0)return e.value},Z=t=>{const e=t.pageProgressionDirection?.trim().toLowerCase(),o=e==="ltr"||e==="rtl"?e:void 0,n=t.renditionLayoutMeta?.trim().toLowerCase(),i=n==="reflowable"||n==="pre-paginated"?n:void 0,s=W(t.identifiers),a=l(s),c=v(s);return{...c!==void 0?{gtin:c}:{},...a!==void 0?{isbn:a}:{},...o!==void 0?{readingDirection:o}:{},...i!==void 0?{renditionLayout:i}:{}}},ee=t=>t.kind==="comicInfo"?J(t):t.kind==="kobo"?Q(t):t.kind==="apple"?j(t):Z(t);r.APPLE_IBOOKS_DISPLAY_OPTIONS_FILENAME=A,r.COMIC_INFO_FILENAME=I,r.COMIC_INFO_MANGA_VALUES=h,r.KOBO_DISPLAY_OPTIONS_FILENAME=F,r.isComicInfoManga=b,r.normalizeGtin=v,r.normalizeIsbn=l,r.parseAppleDisplayOptionsXml=M,r.parseComicInfo=k,r.parseKoboXml=R,r.parseOpf=H,r.resolveArchiveMetadata=ee,r.tokenizeXmlSpaceSeparatedList=L,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})}));
|
|
2
|
+
//# sourceMappingURL=index.umd.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.cjs","sources":["../src/apple/parse.ts","../src/comicInfo/manga.ts","../src/comicInfo/parse.ts","../src/kobo/parse.ts","../src/utils/tokenizeXmlSpaceSeparatedList.ts","../src/opf/spineItemrefProperties.ts","../src/opf/parse.ts","../src/apple/resolve.ts","../src/utils/normalizeGtin.ts","../src/utils/normalizeIsbn.ts","../src/comicInfo/resolve.ts","../src/kobo/resolve.ts","../src/opf/resolve.ts","../src/resolve.ts"],"sourcesContent":["import type { XmlElement } from \"xmldoc\"\nimport { XmlDocument } from \"xmldoc\"\n\nexport const APPLE_IBOOKS_DISPLAY_OPTIONS_FILENAME =\n \"com.apple.ibooks.display-options.xml\"\n\nexport type AppleDisplayOption = {\n readonly name?: string\n readonly value: string\n}\n\nexport type AppleMetadata = {\n readonly kind: \"apple\"\n readonly displayOptions?: {\n readonly platform?: {\n readonly options: ReadonlyArray<AppleDisplayOption>\n }\n }\n}\n\nconst platformOptionsFromElement = (\n platform: XmlElement,\n): ReadonlyArray<AppleDisplayOption> =>\n platform.childrenNamed(\"option\").map((option) => ({\n name: option.attr?.name,\n value: option.val,\n }))\n\nexport const parseAppleDisplayOptionsXml = (xml: string): AppleMetadata => {\n const doc = new XmlDocument(xml)\n const root = doc.name?.toLowerCase()\n\n if (root !== \"display_options\") {\n return { kind: \"apple\" }\n }\n\n const platformEl = doc.childNamed(\"platform\")\n\n return {\n kind: \"apple\",\n displayOptions: {\n ...(platformEl !== undefined\n ? { platform: { options: platformOptionsFromElement(platformEl) } }\n : {}),\n },\n }\n}\n","/**\n * Allowed `Manga` element values (ComicInfo 2.0 `Manga` simpleType).\n *\n * @see https://anansi-project.github.io/docs/comicinfo/documentation#manga\n */\nexport const COMIC_INFO_MANGA_VALUES = [\n \"Unknown\",\n \"No\",\n \"Yes\",\n \"YesAndRightToLeft\",\n] as const\n\nexport type ComicInfoManga = (typeof COMIC_INFO_MANGA_VALUES)[number]\n\nexport const isComicInfoManga = (value: string): value is ComicInfoManga => {\n for (const v of COMIC_INFO_MANGA_VALUES) {\n if (v === value) return true\n }\n return false\n}\n","import type { XmlElement } from \"xmldoc\"\nimport { XmlDocument } from \"xmldoc\"\nimport type { ComicInfoManga } from \"./manga\"\n\n/** Canonical top-level filename; real archives may use any casing. */\nexport const COMIC_INFO_FILENAME = \"ComicInfo.xml\"\n\n/**\n * Parsed `ComicInfo.xml` root: one optional string property per child element,\n * using the same names as in the file (e.g. `Title`, `GTIN`, `LanguageISO`).\n * Nested blocks such as `Pages` are skipped. Other simple child elements are\n * still copied onto this object under their tag name.\n *\n * @see https://anansi-project.github.io/docs/comicinfo/intro\n * @see https://github.com/anansi-project/comicinfo/blob/main/drafts/v2.1/ComicInfo.xsd for schema\n */\nexport interface ComicInfo {\n readonly kind: \"comicInfo\"\n AgeRating?: string\n AlternateCount?: string\n AlternateNumber?: string\n AlternateSeries?: string\n BlackAndWhite?: string\n Characters?: string\n Colorist?: string\n CommunityRating?: string\n Count?: string\n CoverArtist?: string\n Day?: string\n Editor?: string\n Format?: string\n Genre?: string\n GTIN?: string\n Imprint?: string\n Inker?: string\n LanguageISO?: string\n Letterer?: string\n Locations?: string\n MainCharacterOrTeam?: string\n /** Schema literals per {@link ComicInfoManga}; files may still use other strings. */\n Manga?: ComicInfoManga | (string & {})\n Month?: string\n Notes?: string\n Number?: string\n PageCount?: string\n Penciller?: string\n Publisher?: string\n Review?: string\n ScanInformation?: string\n Series?: string\n SeriesGroup?: string\n StoryArc?: string\n StoryArcNumber?: string\n Summary?: string\n Tags?: string\n Teams?: string\n Title?: string\n Translator?: string\n Volume?: string\n Web?: string\n Writer?: string\n Year?: string\n [tag: string]: string | undefined\n}\n\nconst SKIP_ELEMENT_CHILDREN = new Set([\"Pages\", \"ComicInfo\"])\n\nconst hasNestedElement = (el: XmlElement) =>\n el.children.some((c) => c.type === \"element\")\n\nconst trimmedText = (el: XmlElement): string | undefined => {\n const t = el.val.trim()\n return t.length > 0 ? t : undefined\n}\n\n/**\n * Parse a raw `ComicInfo.xml` body. Each direct child element with plain text\n * becomes a property named after that tag. Malformed XML throws; the parser\n * error is attached as `cause`.\n */\nexport const parseComicInfo = (xml: string): ComicInfo => {\n let doc: XmlDocument\n try {\n doc = new XmlDocument(xml)\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause)\n throw new Error(`${COMIC_INFO_FILENAME} is malformed: ${message}`, {\n cause,\n })\n }\n\n const fields: Record<string, string> = {}\n\n doc.eachChild((child) => {\n if (child.type !== \"element\") return\n if (SKIP_ELEMENT_CHILDREN.has(child.name)) return\n if (hasNestedElement(child)) return\n\n const text = trimmedText(child)\n if (text === undefined) return\n if (fields[child.name] !== undefined) return\n\n fields[child.name] = text\n })\n\n // `as ComicInfo`: TS cannot infer that spreading `Record<string, string>` into `{ kind }` satisfies the named optional fields plus `[tag: string]: string | undefined` on `ComicInfo`.\n return { kind: \"comicInfo\", ...fields } as ComicInfo\n}\n","import { XmlDocument } from \"xmldoc\"\n\nexport const KOBO_DISPLAY_OPTIONS_FILENAME = \"com.kobobooks.display-options.xml\"\n\n/**\n * Normalized fields from Kobo-specific XML sidecars. Grows as new Kobo\n * document kinds are supported; absent keys mean not present or unknown.\n */\nexport type KoboMetadata = {\n readonly kind: \"kobo\"\n renditionLayout?: `reflowable` | `pre-paginated`\n}\n\nconst parseKoboDisplayOptionsDocument = (\n doc: XmlDocument,\n): { renditionLayout?: \"pre-paginated\" } => {\n const platform = doc.childNamed(\"platform\")\n if (!platform) return {}\n\n for (const option of platform.childrenNamed(\"option\")) {\n if (option.attr?.name !== \"fixed-layout\") continue\n if (option.val.trim().toLowerCase() === \"true\") {\n return { renditionLayout: \"pre-paginated\" }\n }\n return {}\n }\n\n return {}\n}\n\n/**\n * Parse a Kobo-related XML document. Unsupported or unrecognized roots\n * yield an empty object. Malformed XML throws from the underlying parser.\n */\nexport const parseKoboXml = (xml: string): KoboMetadata => {\n const doc = new XmlDocument(xml)\n const root = doc.name?.toLowerCase()\n\n if (root === \"display_options\") {\n return { kind: \"kobo\", ...parseKoboDisplayOptionsDocument(doc) }\n }\n\n return { kind: \"kobo\" }\n}\n","/**\n * EPUB/XML space-separated token lists (e.g. `properties` on `item` / `itemref`).\n * Trims the raw string, splits on ASCII whitespace, drops empty segments.\n */\nexport const tokenizeXmlSpaceSeparatedList = (\n raw: string | undefined,\n): readonly string[] => {\n if (raw === undefined || raw.trim().length === 0) {\n return []\n }\n\n return raw\n .trim()\n .split(/\\s+/)\n .filter((t) => t.length > 0)\n}\n","import { tokenizeXmlSpaceSeparatedList } from \"../utils/tokenizeXmlSpaceSeparatedList\"\n\nexport type OpfItemrefLayoutHints = {\n readonly renditionLayout?: `reflowable` | `pre-paginated`\n readonly pageSpreadLeft?: true\n readonly pageSpreadRight?: true\n}\n\n/**\n * EPUB `itemref` `properties` attribute (space-separated tokens).\n *\n * @see https://www.w3.org/TR/epub/#attrdef-properties\n */\nexport const layoutHintsFromItemrefProperties = (\n properties: string | undefined,\n): OpfItemrefLayoutHints => {\n const tokens = tokenizeXmlSpaceSeparatedList(properties)\n if (tokens.length === 0) {\n return {}\n }\n\n let renditionLayout: `reflowable` | `pre-paginated` | undefined\n if (tokens.includes(`rendition:layout-reflowable`)) {\n renditionLayout = `reflowable`\n }\n if (tokens.includes(`rendition:layout-pre-paginated`)) {\n renditionLayout = `pre-paginated`\n }\n\n return {\n ...(renditionLayout !== undefined ? { renditionLayout } : {}),\n ...(tokens.includes(`page-spread-left`)\n ? { pageSpreadLeft: true as const }\n : {}),\n ...(tokens.includes(`page-spread-right`)\n ? { pageSpreadRight: true as const }\n : {}),\n }\n}\n","import type { XmlElement, XmlNodeBase } from \"xmldoc\"\nimport { XmlDocument } from \"xmldoc\"\nimport { layoutHintsFromItemrefProperties } from \"./spineItemrefProperties\"\n\nexport type OpfSpineManifestItem = {\n readonly id: string\n readonly href: string\n readonly mediaType?: string\n readonly properties?: string\n}\n\nexport type OpfIdentifier = {\n readonly value: string\n readonly scheme?: string\n}\n\nexport type OpfSpineRow = {\n readonly idref: string\n readonly id: string\n readonly href: string\n readonly mediaType?: string\n readonly properties?: string\n readonly renditionLayout?: `reflowable` | `pre-paginated`\n readonly pageSpreadLeft?: true\n readonly pageSpreadRight?: true\n}\n\nexport type OpfGuideReference = {\n readonly href: string\n readonly title: string\n readonly type: string\n}\n\nconst elementLocalName = (name: string): string =>\n name.includes(\":\") ? name.slice(name.lastIndexOf(\":\") + 1) : name\n\nconst localNameEq = (elementName: string, wantLocal: string): boolean =>\n elementLocalName(elementName).toLowerCase() === wantLocal.toLowerCase()\n\nconst isXmlElement = (node: XmlNodeBase): node is XmlElement =>\n node.type === \"element\"\n\nconst childNamedLocal = (\n parent: XmlElement,\n localName: string,\n): XmlElement | undefined => {\n for (const node of parent.children) {\n if (!isXmlElement(node)) continue\n if (localNameEq(node.name, localName)) return node\n }\n return undefined\n}\n\nconst childrenNamedLocal = (\n parent: XmlElement,\n localName: string,\n): XmlElement[] => {\n const out: XmlElement[] = []\n for (const node of parent.children) {\n if (!isXmlElement(node)) continue\n if (localNameEq(node.name, localName)) out.push(node)\n }\n return out\n}\n\nconst identifiersFromMetadata = (metadataEl: XmlElement): OpfIdentifier[] => {\n const identifiers: OpfIdentifier[] = []\n\n metadataEl.eachChild((child) => {\n if (elementLocalName(child.name).toLowerCase() !== \"identifier\") return\n\n const value = child.val.trim()\n if (value.length === 0) return\n\n const scheme =\n child.attr[\"opf:scheme\"] ?? child.attr[\"opf:Scheme\"] ?? child.attr.scheme\n const schemeTrimmed = scheme?.trim()\n\n identifiers.push({\n value,\n ...(schemeTrimmed !== undefined && schemeTrimmed.length > 0\n ? { scheme: schemeTrimmed }\n : {}),\n })\n })\n\n return identifiers\n}\n\nconst titleFromMetadata = (metadataEl: XmlElement): string | undefined => {\n let found: string | undefined\n metadataEl.eachChild((child) => {\n if (found !== undefined) return\n if (elementLocalName(child.name).toLowerCase() !== \"title\") return\n const t = child.val.trim()\n if (t.length > 0) found = t\n })\n return found\n}\n\nconst metaValByProperty = (\n metadataEl: XmlElement,\n property: string,\n): string | undefined => {\n const meta = childrenNamedLocal(metadataEl, \"meta\").find(\n (m) => m.attr.property === property,\n )\n const raw = meta?.val\n if (raw === undefined || raw.trim().length === 0) return undefined\n return raw\n}\n\nconst guideFromPackage = (doc: XmlElement): OpfGuideReference[] => {\n const guideEl = childNamedLocal(doc, \"guide\")\n if (guideEl === undefined) return []\n\n const refs: OpfGuideReference[] = []\n\n for (const ref of childrenNamedLocal(guideEl, \"reference\")) {\n const href = ref.attr.href?.trim()\n if (href === undefined || href.length === 0) continue\n refs.push({\n href,\n title: ref.attr.title?.trim() ?? ``,\n type: ref.attr.type?.trim() ?? ``,\n })\n }\n\n return refs\n}\n\nconst manifestItemFromXmlElement = (\n item: XmlElement,\n): OpfSpineManifestItem | undefined => {\n const id = item.attr.id\n const href = item.attr.href\n if (id === undefined || id.length === 0) return undefined\n if (href === undefined || href.length === 0) return undefined\n\n const mediaType = item.attr[\"media-type\"]\n const properties = item.attr.properties?.trim()\n return {\n id,\n href,\n ...(mediaType !== undefined && mediaType.length > 0 ? { mediaType } : {}),\n ...(properties !== undefined && properties.length > 0\n ? { properties }\n : {}),\n }\n}\n\nconst manifestItemsAndById = (\n manifestEl: XmlElement,\n): {\n items: OpfSpineManifestItem[]\n byId: Map<string, OpfSpineManifestItem>\n} => {\n const items: OpfSpineManifestItem[] = []\n const byId = new Map<string, OpfSpineManifestItem>()\n\n for (const el of childrenNamedLocal(manifestEl, \"item\")) {\n const parsed = manifestItemFromXmlElement(el)\n if (parsed === undefined) continue\n items.push(parsed)\n byId.set(parsed.id, parsed)\n }\n\n return { items, byId }\n}\n\nconst spineRowsFromByIdAndSpine = (\n byId: Map<string, OpfSpineManifestItem>,\n spineEl: XmlElement,\n): OpfSpineRow[] => {\n const rows: OpfSpineRow[] = []\n\n for (const itemref of childrenNamedLocal(spineEl, \"itemref\")) {\n const idref = itemref.attr.idref\n if (idref === undefined || idref.trim().length === 0) continue\n\n const manifestItem = byId.get(idref)\n if (manifestItem === undefined) continue\n\n const hints = layoutHintsFromItemrefProperties(itemref.attr.properties)\n\n rows.push({\n idref,\n id: manifestItem.id,\n href: manifestItem.href,\n ...(manifestItem.mediaType !== undefined\n ? { mediaType: manifestItem.mediaType }\n : {}),\n ...(manifestItem.properties !== undefined\n ? { properties: manifestItem.properties }\n : {}),\n ...(hints.renditionLayout !== undefined\n ? { renditionLayout: hints.renditionLayout }\n : {}),\n ...(hints.pageSpreadLeft !== undefined\n ? { pageSpreadLeft: hints.pageSpreadLeft }\n : {}),\n ...(hints.pageSpreadRight !== undefined\n ? { pageSpreadRight: hints.pageSpreadRight }\n : {}),\n })\n }\n\n return rows\n}\n\nexport type OpfMetadata = {\n readonly kind: \"opf\"\n readonly manifestItems: ReadonlyArray<OpfSpineManifestItem>\n readonly spineRows: ReadonlyArray<OpfSpineRow>\n readonly spineTocIdref: string | undefined\n readonly identifiers: ReadonlyArray<OpfIdentifier>\n readonly title: string | undefined\n readonly renditionLayoutMeta: string | undefined\n readonly renditionFlowMeta: string | undefined\n readonly renditionSpreadMeta: string | undefined\n readonly pageProgressionDirection: string | undefined\n readonly guide: ReadonlyArray<OpfGuideReference>\n}\n\n/**\n * Parses an EPUB package document (OPF) into structured metadata.\n *\n * Direct children of `package` (`metadata`, `manifest`, `spine`, `guide`) and\n * their structural children (`item`, `itemref`, `reference`, `meta`) are\n * matched by **local name** (ASCII case-insensitive), so prefixed tags such as\n * `opf:manifest` are supported the same as unprefixed `manifest`.\n *\n * Attribute names on `spine` / `itemref` are still read as emitted by xmldoc\n * (no QName normalization).\n */\nexport const parseOpf = (opfXml: string): OpfMetadata => {\n const doc = new XmlDocument(opfXml)\n const manifestEl = childNamedLocal(doc, \"manifest\")\n const spineEl = childNamedLocal(doc, \"spine\")\n const metadataEl = childNamedLocal(doc, \"metadata\")\n\n let manifestItems: OpfSpineManifestItem[] = []\n let spineRows: OpfSpineRow[] = []\n\n if (manifestEl !== undefined) {\n const { items, byId } = manifestItemsAndById(manifestEl)\n manifestItems = items\n if (spineEl !== undefined) {\n spineRows = spineRowsFromByIdAndSpine(byId, spineEl)\n }\n }\n\n const pageProgressionDirectionRaw =\n spineEl?.attr[\"page-progression-direction\"]\n const pageProgressionDirection =\n pageProgressionDirectionRaw !== undefined &&\n pageProgressionDirectionRaw.trim().length > 0\n ? pageProgressionDirectionRaw\n : undefined\n\n const spineTocRaw = spineEl?.attr.toc\n const spineTocIdref =\n spineTocRaw !== undefined && spineTocRaw.trim().length > 0\n ? spineTocRaw.trim()\n : undefined\n\n let title: string | undefined\n let renditionLayoutMeta: string | undefined\n let renditionFlowMeta: string | undefined\n let renditionSpreadMeta: string | undefined\n const identifiers: OpfIdentifier[] = []\n\n if (metadataEl !== undefined) {\n title = titleFromMetadata(metadataEl)\n renditionLayoutMeta = metaValByProperty(metadataEl, \"rendition:layout\")\n renditionFlowMeta = metaValByProperty(metadataEl, \"rendition:flow\")\n renditionSpreadMeta = metaValByProperty(metadataEl, \"rendition:spread\")\n identifiers.push(...identifiersFromMetadata(metadataEl))\n }\n\n const guide = guideFromPackage(doc)\n\n return {\n kind: \"opf\",\n manifestItems,\n spineRows,\n spineTocIdref,\n identifiers,\n title,\n renditionLayoutMeta,\n renditionFlowMeta,\n renditionSpreadMeta,\n pageProgressionDirection,\n guide,\n }\n}\n","import type { ArchiveResolveResult } from \"../types/archiveResolve\"\nimport type { AppleMetadata } from \"./parse\"\n\nexport const resolveApple = (input: AppleMetadata): ArchiveResolveResult => {\n const fixedLayout = input.displayOptions?.platform?.options?.find(\n (o) => o.name === \"fixed-layout\",\n )?.value\n\n if (fixedLayout === undefined) return {}\n\n const renditionLayout =\n fixedLayout.trim().toLowerCase() === \"true\"\n ? (\"pre-paginated\" as const)\n : undefined\n\n return renditionLayout !== undefined ? { renditionLayout } : {}\n}\n","const GTIN_LENGTHS = new Set([8, 12, 13, 14])\n\n/**\n * Normalize a raw GTIN / EAN / UPC string to digits only when the length\n * matches a GS1 GTIN family size (8, 12, 13, or 14). Does not verify check digits.\n */\nexport const normalizeGtin = (\n raw: string | number | undefined | null,\n): string | undefined => {\n if (raw === undefined || raw === null) return undefined\n\n const digits = String(raw).replace(/\\D/g, \"\")\n if (digits.length === 0 || !GTIN_LENGTHS.has(digits.length)) return undefined\n\n return digits\n}\n","const ISBN_CANDIDATE_PATTERN = /(?:97[89])?\\d{9}[\\dXx]/\n\n/**\n * Normalize a raw ISBN-ish string into a canonical 10- or 13-character\n * form, or `undefined` when no recognisable ISBN can be recovered.\n *\n * - Strips the common `urn:isbn:` / `isbn:` prefixes.\n * - Drops everything that isn't a digit or `X`.\n * - Validates the resulting length (10 or 13).\n * - Falls back to a lax regex scan so publishers that stuff free text\n * around the number still yield a usable value.\n */\nexport const normalizeIsbn = (\n raw: string | number | undefined | null,\n): string | undefined => {\n if (raw === undefined || raw === null) return undefined\n\n const stripped = String(raw)\n .trim()\n .replace(/^urn:isbn:/i, \"\")\n .replace(/^isbn[:\\s-]*/i, \"\")\n\n const digitsOnly = stripped.replace(/[^0-9Xx]/g, \"\")\n\n if (digitsOnly.length === 10 || digitsOnly.length === 13) {\n return digitsOnly.toUpperCase()\n }\n\n const match = stripped.match(ISBN_CANDIDATE_PATTERN)\n if (match) return match[0].toUpperCase()\n\n return undefined\n}\n","import type { ArchiveResolveResult } from \"../types/archiveResolve\"\nimport { normalizeGtin } from \"../utils/normalizeGtin\"\nimport { normalizeIsbn } from \"../utils/normalizeIsbn\"\nimport type { ComicInfo } from \"./parse\"\n\nconst readingDirection = (info: ComicInfo): \"ltr\" | \"rtl\" =>\n info.Manga === \"YesAndRightToLeft\" ? \"rtl\" : \"ltr\"\n\nexport const resolveComicInfo = (info: ComicInfo): ArchiveResolveResult => {\n const raw = info.GTIN\n const gtin = normalizeGtin(raw)\n const isbn = normalizeIsbn(raw)\n return {\n ...(gtin !== undefined ? { gtin } : {}),\n ...(isbn !== undefined ? { isbn } : {}),\n readingDirection: readingDirection(info),\n }\n}\n","import type { ArchiveResolveResult } from \"../types/archiveResolve\"\nimport type { KoboMetadata } from \"./parse\"\n\nexport const resolveKobo = (input: KoboMetadata): ArchiveResolveResult => {\n const { renditionLayout } = input\n return renditionLayout !== undefined ? { renditionLayout } : {}\n}\n","import type { ArchiveResolveResult } from \"../types/archiveResolve\"\nimport { normalizeGtin } from \"../utils/normalizeGtin\"\nimport { normalizeIsbn } from \"../utils/normalizeIsbn\"\nimport type { OpfIdentifier, OpfMetadata } from \"./parse\"\n\nconst rawIdentifierValueForIsbn = (\n identifiers: ReadonlyArray<OpfIdentifier>,\n): string | undefined => {\n for (const i of identifiers) {\n if (i.scheme !== undefined && i.scheme.trim().toLowerCase() === \"isbn\") {\n if (normalizeIsbn(i.value) !== undefined) return i.value\n }\n }\n\n for (const i of identifiers) {\n if (normalizeIsbn(i.value) !== undefined) return i.value\n }\n\n return undefined\n}\n\nexport const resolveOpf = (input: OpfMetadata): ArchiveResolveResult => {\n const ppd = input.pageProgressionDirection?.trim().toLowerCase()\n const readingDirection = ppd === \"ltr\" || ppd === \"rtl\" ? ppd : undefined\n\n const rl = input.renditionLayoutMeta?.trim().toLowerCase()\n const renditionLayout =\n rl === \"reflowable\" || rl === \"pre-paginated\" ? rl : undefined\n\n const raw = rawIdentifierValueForIsbn(input.identifiers)\n const isbn = normalizeIsbn(raw)\n const gtin = normalizeGtin(raw)\n\n return {\n ...(gtin !== undefined ? { gtin } : {}),\n ...(isbn !== undefined ? { isbn } : {}),\n ...(readingDirection !== undefined ? { readingDirection } : {}),\n ...(renditionLayout !== undefined ? { renditionLayout } : {}),\n }\n}\n","import type { AppleMetadata } from \"./apple/parse\"\nimport { resolveApple } from \"./apple/resolve\"\nimport type { ComicInfo } from \"./comicInfo/parse\"\nimport { resolveComicInfo } from \"./comicInfo/resolve\"\nimport type { KoboMetadata } from \"./kobo/parse\"\nimport { resolveKobo } from \"./kobo/resolve\"\nimport type { OpfMetadata } from \"./opf/parse\"\nimport { resolveOpf } from \"./opf/resolve\"\nimport type { ArchiveResolveResult } from \"./types/archiveResolve\"\n\nexport type ResolvedArchiveInput =\n | ComicInfo\n | KoboMetadata\n | AppleMetadata\n | OpfMetadata\n\nexport const resolveArchiveMetadata = (\n input: ResolvedArchiveInput,\n): ArchiveResolveResult => {\n if (input.kind === \"comicInfo\") return resolveComicInfo(input)\n if (input.kind === \"kobo\") return resolveKobo(input)\n if (input.kind === \"apple\") return resolveApple(input)\n return resolveOpf(input)\n}\n"],"names":["APPLE_IBOOKS_DISPLAY_OPTIONS_FILENAME","platformOptionsFromElement","platform","option","parseAppleDisplayOptionsXml","xml","doc","XmlDocument","platformEl","COMIC_INFO_MANGA_VALUES","isComicInfoManga","value","v","COMIC_INFO_FILENAME","SKIP_ELEMENT_CHILDREN","hasNestedElement","el","c","trimmedText","t","parseComicInfo","cause","message","fields","child","text","KOBO_DISPLAY_OPTIONS_FILENAME","parseKoboDisplayOptionsDocument","parseKoboXml","tokenizeXmlSpaceSeparatedList","raw","layoutHintsFromItemrefProperties","properties","tokens","renditionLayout","elementLocalName","name","localNameEq","elementName","wantLocal","isXmlElement","node","childNamedLocal","parent","localName","childrenNamedLocal","out","identifiersFromMetadata","metadataEl","identifiers","schemeTrimmed","titleFromMetadata","found","metaValByProperty","property","m","guideFromPackage","guideEl","refs","ref","href","manifestItemFromXmlElement","item","id","mediaType","manifestItemsAndById","manifestEl","items","byId","parsed","spineRowsFromByIdAndSpine","spineEl","rows","itemref","idref","manifestItem","hints","parseOpf","opfXml","manifestItems","spineRows","pageProgressionDirectionRaw","pageProgressionDirection","spineTocRaw","spineTocIdref","title","renditionLayoutMeta","renditionFlowMeta","renditionSpreadMeta","guide","resolveApple","input","fixedLayout","o","GTIN_LENGTHS","normalizeGtin","digits","ISBN_CANDIDATE_PATTERN","normalizeIsbn","stripped","digitsOnly","match","readingDirection","info","resolveComicInfo","gtin","isbn","resolveKobo","rawIdentifierValueForIsbn","i","resolveOpf","ppd","rl","resolveArchiveMetadata"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const KOBO_DISPLAY_OPTIONS_FILENAME = "com.kobobooks.display-options.xml";
|
|
2
|
+
/**
|
|
3
|
+
* Normalized fields from Kobo-specific XML sidecars. Grows as new Kobo
|
|
4
|
+
* document kinds are supported; absent keys mean not present or unknown.
|
|
5
|
+
*/
|
|
6
|
+
export type KoboMetadata = {
|
|
7
|
+
readonly kind: "kobo";
|
|
8
|
+
renditionLayout?: `reflowable` | `pre-paginated`;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Parse a Kobo-related XML document. Unsupported or unrecognized roots
|
|
12
|
+
* yield an empty object. Malformed XML throws from the underlying parser.
|
|
13
|
+
*/
|
|
14
|
+
export declare const parseKoboXml: (xml: string) => KoboMetadata;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type OpfSpineManifestItem = {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly href: string;
|
|
4
|
+
readonly mediaType?: string;
|
|
5
|
+
readonly properties?: string;
|
|
6
|
+
};
|
|
7
|
+
export type OpfIdentifier = {
|
|
8
|
+
readonly value: string;
|
|
9
|
+
readonly scheme?: string;
|
|
10
|
+
};
|
|
11
|
+
export type OpfSpineRow = {
|
|
12
|
+
readonly idref: string;
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly href: string;
|
|
15
|
+
readonly mediaType?: string;
|
|
16
|
+
readonly properties?: string;
|
|
17
|
+
readonly renditionLayout?: `reflowable` | `pre-paginated`;
|
|
18
|
+
readonly pageSpreadLeft?: true;
|
|
19
|
+
readonly pageSpreadRight?: true;
|
|
20
|
+
};
|
|
21
|
+
export type OpfGuideReference = {
|
|
22
|
+
readonly href: string;
|
|
23
|
+
readonly title: string;
|
|
24
|
+
readonly type: string;
|
|
25
|
+
};
|
|
26
|
+
export type OpfMetadata = {
|
|
27
|
+
readonly kind: "opf";
|
|
28
|
+
readonly manifestItems: ReadonlyArray<OpfSpineManifestItem>;
|
|
29
|
+
readonly spineRows: ReadonlyArray<OpfSpineRow>;
|
|
30
|
+
readonly spineTocIdref: string | undefined;
|
|
31
|
+
readonly identifiers: ReadonlyArray<OpfIdentifier>;
|
|
32
|
+
readonly title: string | undefined;
|
|
33
|
+
readonly renditionLayoutMeta: string | undefined;
|
|
34
|
+
readonly renditionFlowMeta: string | undefined;
|
|
35
|
+
readonly renditionSpreadMeta: string | undefined;
|
|
36
|
+
readonly pageProgressionDirection: string | undefined;
|
|
37
|
+
readonly guide: ReadonlyArray<OpfGuideReference>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Parses an EPUB package document (OPF) into structured metadata.
|
|
41
|
+
*
|
|
42
|
+
* Direct children of `package` (`metadata`, `manifest`, `spine`, `guide`) and
|
|
43
|
+
* their structural children (`item`, `itemref`, `reference`, `meta`) are
|
|
44
|
+
* matched by **local name** (ASCII case-insensitive), so prefixed tags such as
|
|
45
|
+
* `opf:manifest` are supported the same as unprefixed `manifest`.
|
|
46
|
+
*
|
|
47
|
+
* Attribute names on `spine` / `itemref` are still read as emitted by xmldoc
|
|
48
|
+
* (no QName normalization).
|
|
49
|
+
*/
|
|
50
|
+
export declare const parseOpf: (opfXml: string) => OpfMetadata;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type OpfItemrefLayoutHints = {
|
|
2
|
+
readonly renditionLayout?: `reflowable` | `pre-paginated`;
|
|
3
|
+
readonly pageSpreadLeft?: true;
|
|
4
|
+
readonly pageSpreadRight?: true;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* EPUB `itemref` `properties` attribute (space-separated tokens).
|
|
8
|
+
*
|
|
9
|
+
* @see https://www.w3.org/TR/epub/#attrdef-properties
|
|
10
|
+
*/
|
|
11
|
+
export declare const layoutHintsFromItemrefProperties: (properties: string | undefined) => OpfItemrefLayoutHints;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AppleMetadata } from './apple/parse';
|
|
2
|
+
import { ComicInfo } from './comicInfo/parse';
|
|
3
|
+
import { KoboMetadata } from './kobo/parse';
|
|
4
|
+
import { OpfMetadata } from './opf/parse';
|
|
5
|
+
import { ArchiveResolveResult } from './types/archiveResolve';
|
|
6
|
+
export type ResolvedArchiveInput = ComicInfo | KoboMetadata | AppleMetadata | OpfMetadata;
|
|
7
|
+
export declare const resolveArchiveMetadata: (input: ResolvedArchiveInput) => ArchiveResolveResult;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-format hints for manifest-style consumers (identifiers, reading order,
|
|
3
|
+
* fixed-layout). Only keys with a defined value are set.
|
|
4
|
+
*
|
|
5
|
+
* @see https://en.wikipedia.org/wiki/ISBN
|
|
6
|
+
*/
|
|
7
|
+
export type ArchiveResolveResult = {
|
|
8
|
+
/** Digits-only GTIN when the source matches a GS1 length (8 / 12 / 13 / 14). */
|
|
9
|
+
gtin?: string;
|
|
10
|
+
isbn?: string;
|
|
11
|
+
readingDirection?: "ltr" | "rtl";
|
|
12
|
+
renditionLayout?: "reflowable" | "pre-paginated";
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a raw ISBN-ish string into a canonical 10- or 13-character
|
|
3
|
+
* form, or `undefined` when no recognisable ISBN can be recovered.
|
|
4
|
+
*
|
|
5
|
+
* - Strips the common `urn:isbn:` / `isbn:` prefixes.
|
|
6
|
+
* - Drops everything that isn't a digit or `X`.
|
|
7
|
+
* - Validates the resulting length (10 or 13).
|
|
8
|
+
* - Falls back to a lax regex scan so publishers that stuff free text
|
|
9
|
+
* around the number still yield a usable value.
|
|
10
|
+
*/
|
|
11
|
+
export declare const normalizeIsbn: (raw: string | number | undefined | null) => string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prose-reader/archive-parser",
|
|
3
|
+
"version": "1.287.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.umd.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.umd.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"files": [
|
|
20
|
+
"/dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "vite build --watch --mode development",
|
|
24
|
+
"build": "tsc && vite build",
|
|
25
|
+
"test": "vitest run --coverage",
|
|
26
|
+
"tsc": "tsc",
|
|
27
|
+
"test:watch": "vitest watch"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"xmldoc": "^2.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|