@likecoin/epubcheck-ts 0.6.0 → 0.6.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/bin/epubcheck.js +15 -2
- package/bin/epubcheck.ts +15 -2
- package/dist/index.cjs +171 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +171 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/bin/epubcheck.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { basename, join, relative, sep } from "node:path";
|
|
5
|
-
const { EpubCheck, EPUB_VERSIONS, toJSONReport } = await import("../dist/index.js");
|
|
6
|
-
const VERSION = "0.6.
|
|
5
|
+
const { EpubCheck, EPUB_VERSIONS, MessageId, toJSONReport } = await import("../dist/index.js");
|
|
6
|
+
const VERSION = "0.6.1";
|
|
7
7
|
const VALID_MODES = /* @__PURE__ */ new Set([
|
|
8
8
|
"exp",
|
|
9
9
|
"opf",
|
|
@@ -301,6 +301,19 @@ async function main() {
|
|
|
301
301
|
const shouldFail = result.errorCount > 0 || result.fatalCount > 0 || failOnWarnings && result.warningCount > 0;
|
|
302
302
|
process.exit(shouldFail ? 1 : 0);
|
|
303
303
|
} catch (error) {
|
|
304
|
+
const code = error?.code;
|
|
305
|
+
if (code === "ENOENT") {
|
|
306
|
+
console.error(`\x1B[31m\x1B[1mFATAL (${filePath}):\x1B[0m EPUB file could not be found`);
|
|
307
|
+
console.error(` \x1B[90mID: ${MessageId.PKG_018}\x1B[0m`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
if (code === "EACCES" || code === "EISDIR" || code === "EIO") {
|
|
311
|
+
console.error(
|
|
312
|
+
`\x1B[31m\x1B[1mFATAL (${filePath}):\x1B[0m Unable to read EPUB contents: ${error instanceof Error ? error.message : String(error)}`
|
|
313
|
+
);
|
|
314
|
+
console.error(` \x1B[90mID: ${MessageId.PKG_015}\x1B[0m`);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
304
317
|
console.error("\x1B[31mError:\x1B[0m", error instanceof Error ? error.message : String(error));
|
|
305
318
|
if (error instanceof Error && error.stack && !values.quiet) {
|
|
306
319
|
console.error("\x1B[90m" + error.stack + "\x1B[0m");
|
package/bin/epubcheck.ts
CHANGED
|
@@ -20,9 +20,9 @@ import type {
|
|
|
20
20
|
} from '../src/types.js';
|
|
21
21
|
|
|
22
22
|
// Dynamic import to support both ESM and CJS builds
|
|
23
|
-
const { EpubCheck, EPUB_VERSIONS, toJSONReport } = await import('../dist/index.js');
|
|
23
|
+
const { EpubCheck, EPUB_VERSIONS, MessageId, toJSONReport } = await import('../dist/index.js');
|
|
24
24
|
|
|
25
|
-
const VERSION = '0.6.
|
|
25
|
+
const VERSION = '0.6.1';
|
|
26
26
|
const VALID_MODES: ReadonlySet<ValidationMode> = new Set([
|
|
27
27
|
'exp',
|
|
28
28
|
'opf',
|
|
@@ -399,6 +399,19 @@ async function main(): Promise<void> {
|
|
|
399
399
|
result.errorCount > 0 || result.fatalCount > 0 || (failOnWarnings && result.warningCount > 0);
|
|
400
400
|
process.exit(shouldFail ? 1 : 0);
|
|
401
401
|
} catch (error) {
|
|
402
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
403
|
+
if (code === 'ENOENT') {
|
|
404
|
+
console.error(`\x1b[31m\x1b[1mFATAL (${filePath}):\x1b[0m EPUB file could not be found`);
|
|
405
|
+
console.error(` \x1b[90mID: ${MessageId.PKG_018}\x1b[0m`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
if (code === 'EACCES' || code === 'EISDIR' || code === 'EIO') {
|
|
409
|
+
console.error(
|
|
410
|
+
`\x1b[31m\x1b[1mFATAL (${filePath}):\x1b[0m Unable to read EPUB contents: ${error instanceof Error ? error.message : String(error)}`,
|
|
411
|
+
);
|
|
412
|
+
console.error(` \x1b[90mID: ${MessageId.PKG_015}\x1b[0m`);
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
402
415
|
console.error('\x1b[31mError:\x1b[0m', error instanceof Error ? error.message : String(error));
|
|
403
416
|
if (error instanceof Error && error.stack && !values.quiet) {
|
|
404
417
|
console.error('\x1b[90m' + error.stack + '\x1b[0m');
|
package/dist/index.cjs
CHANGED
|
@@ -2693,6 +2693,31 @@ var PROFILE_DC_TYPE = {
|
|
|
2693
2693
|
dict: "dictionary",
|
|
2694
2694
|
preview: "preview"
|
|
2695
2695
|
};
|
|
2696
|
+
var TYPE_TO_PROFILE = {
|
|
2697
|
+
dictionary: "dict",
|
|
2698
|
+
edupub: "edupub",
|
|
2699
|
+
index: "idx",
|
|
2700
|
+
preview: "preview"
|
|
2701
|
+
};
|
|
2702
|
+
var RESERVED_PREFIX_URIS = {
|
|
2703
|
+
dcterms: "http://purl.org/dc/terms/",
|
|
2704
|
+
marc: "http://id.loc.gov/vocabulary/",
|
|
2705
|
+
media: "http://www.idpf.org/epub/vocab/overlays/#",
|
|
2706
|
+
onix: "http://www.editeur.org/ONIX/book/codelists/current.html#",
|
|
2707
|
+
rendition: "http://www.idpf.org/vocab/rendition/#",
|
|
2708
|
+
schema: "http://schema.org/",
|
|
2709
|
+
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
2710
|
+
a11y: "http://www.idpf.org/epub/vocab/package/a11y/#"
|
|
2711
|
+
};
|
|
2712
|
+
function isValidURI(uri) {
|
|
2713
|
+
if (!uri) return false;
|
|
2714
|
+
try {
|
|
2715
|
+
new URL(uri);
|
|
2716
|
+
return true;
|
|
2717
|
+
} catch {
|
|
2718
|
+
return false;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2696
2721
|
var DICTIONARY_TYPE_VALUES = /* @__PURE__ */ new Set([
|
|
2697
2722
|
"monolingual",
|
|
2698
2723
|
"bilingual",
|
|
@@ -2800,11 +2825,13 @@ var OPFValidator = class {
|
|
|
2800
2825
|
this.validatePackageAttributes(context, opfPath);
|
|
2801
2826
|
this.validateMetadata(context, opfPath);
|
|
2802
2827
|
if (this.packageDoc.version !== "2.0") {
|
|
2828
|
+
this.validatePrefixDeclarations(context, opfPath, opfXml);
|
|
2803
2829
|
this.validateMetaPrefixes(context, opfPath, opfXml);
|
|
2804
2830
|
}
|
|
2805
2831
|
this.validateLinkElements(context, opfPath);
|
|
2806
2832
|
this.validateManifest(context, opfPath);
|
|
2807
2833
|
this.validateSpine(context, opfPath);
|
|
2834
|
+
this.validatePageMap(context, opfPath, opfXml);
|
|
2808
2835
|
this.validateFallbackChains(context, opfPath);
|
|
2809
2836
|
this.validateUndeclaredResources(context, opfPath);
|
|
2810
2837
|
if (this.packageDoc.version === "2.0") {
|
|
@@ -2835,6 +2862,7 @@ var OPFValidator = class {
|
|
|
2835
2862
|
if (this.packageDoc.version.startsWith("3.")) {
|
|
2836
2863
|
this.validateAccessibilityMetadata(context, opfPath);
|
|
2837
2864
|
this.validateProfileDcType(context, opfPath);
|
|
2865
|
+
this.validateDcTypeProfileSwitch(context, opfPath);
|
|
2838
2866
|
this.validateEdupubMetadata(context, opfPath);
|
|
2839
2867
|
this.validateDictionaryMetadata(context, opfPath);
|
|
2840
2868
|
this.validatePreviewMetadata(context, opfPath);
|
|
@@ -3038,6 +3066,22 @@ var OPFValidator = class {
|
|
|
3038
3066
|
});
|
|
3039
3067
|
}
|
|
3040
3068
|
}
|
|
3069
|
+
// Mirrors Java's EPUBProfile.makeTypeCompatible flow.
|
|
3070
|
+
validateDcTypeProfileSwitch(context, opfPath) {
|
|
3071
|
+
if (!this.packageDoc) return;
|
|
3072
|
+
for (const dc of this.packageDoc.dcElements) {
|
|
3073
|
+
if (dc.name !== "type") continue;
|
|
3074
|
+
const inferred = TYPE_TO_PROFILE[dc.value.trim().toLowerCase()];
|
|
3075
|
+
if (inferred && inferred !== context.options.profile) {
|
|
3076
|
+
pushMessage(context.messages, {
|
|
3077
|
+
id: MessageId.OPF_064,
|
|
3078
|
+
message: `OPF declares type "${dc.value.trim().toLowerCase()}"; consider validating using the "${inferred}" profile.`,
|
|
3079
|
+
location: { path: opfPath }
|
|
3080
|
+
});
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3041
3085
|
/**
|
|
3042
3086
|
* Build lookup maps for manifest items
|
|
3043
3087
|
*/
|
|
@@ -3054,6 +3098,13 @@ var OPFValidator = class {
|
|
|
3054
3098
|
*/
|
|
3055
3099
|
validatePackageAttributes(context, opfPath) {
|
|
3056
3100
|
if (!this.packageDoc) return;
|
|
3101
|
+
if (this.packageDoc.isLegacyOebps12) {
|
|
3102
|
+
pushMessage(context.messages, {
|
|
3103
|
+
id: MessageId.OPF_047,
|
|
3104
|
+
message: "OPF file is using OEBPS 1.2 syntax allowing backwards compatibility.",
|
|
3105
|
+
location: { path: opfPath }
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3057
3108
|
if (this.packageDoc.versionDeclared === false) {
|
|
3058
3109
|
pushMessage(context.messages, {
|
|
3059
3110
|
id: MessageId.OPF_001,
|
|
@@ -3978,14 +4029,24 @@ var OPFValidator = class {
|
|
|
3978
4029
|
const resolvedPath = resolvePath(opfPath, basePathNoQuery);
|
|
3979
4030
|
const resolvedPathDecoded = basePathDecodedNoQuery !== basePathNoQuery ? resolvePath(opfPath, basePathDecodedNoQuery) : resolvedPath;
|
|
3980
4031
|
const fileExists = context.files.has(resolvedPath) || context.files.has(resolvedPathDecoded);
|
|
3981
|
-
const
|
|
3982
|
-
if (!fileExists && !
|
|
4032
|
+
const manifestItem = this.manifestByHref.get(basePathNoQuery) ?? this.manifestByHref.get(basePathDecodedNoQuery);
|
|
4033
|
+
if (!fileExists && !manifestItem) {
|
|
3983
4034
|
pushMessage(context.messages, {
|
|
3984
4035
|
id: MessageId.RSC_007w,
|
|
3985
4036
|
message: `Referenced resource "${resolvedPath}" could not be found in the EPUB`,
|
|
3986
4037
|
location: { path: opfPath }
|
|
3987
4038
|
});
|
|
3988
4039
|
}
|
|
4040
|
+
if (manifestItem) {
|
|
4041
|
+
const inSpine = this.packageDoc.spine.some((ref) => ref.idref === manifestItem.id);
|
|
4042
|
+
if (!inSpine) {
|
|
4043
|
+
pushMessage(context.messages, {
|
|
4044
|
+
id: MessageId.OPF_067,
|
|
4045
|
+
message: `Resource "${manifestItem.href}" is referenced as a link but is also declared as a manifest item.`,
|
|
4046
|
+
location: { path: opfPath }
|
|
4047
|
+
});
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
3989
4050
|
}
|
|
3990
4051
|
}
|
|
3991
4052
|
/**
|
|
@@ -4267,6 +4328,56 @@ var OPFValidator = class {
|
|
|
4267
4328
|
}
|
|
4268
4329
|
}
|
|
4269
4330
|
}
|
|
4331
|
+
// Mirrors Java's PrefixDeclarationParser + VocabUtil.parsePrefixDeclaration,
|
|
4332
|
+
// but emits only the four main IDs (not Java's OPF-004a..f sub-codes).
|
|
4333
|
+
validatePrefixDeclarations(context, opfPath, opfXml) {
|
|
4334
|
+
const stripped = stripXmlComments(opfXml);
|
|
4335
|
+
const match = /<package[^>]*\sprefix\s*=\s*["']([^"']*)["']/.exec(stripped);
|
|
4336
|
+
if (!match) return;
|
|
4337
|
+
const raw = match[1] ?? "";
|
|
4338
|
+
if (raw !== raw.trim()) {
|
|
4339
|
+
pushMessage(context.messages, {
|
|
4340
|
+
id: MessageId.OPF_004,
|
|
4341
|
+
message: "The value of the prefix attribute has leading or trailing whitespace.",
|
|
4342
|
+
location: { path: opfPath }
|
|
4343
|
+
});
|
|
4344
|
+
}
|
|
4345
|
+
const parts = raw.trim().split(/\s+/).filter(Boolean);
|
|
4346
|
+
for (let i = 0; i < parts.length; ) {
|
|
4347
|
+
const token = parts[i] ?? "";
|
|
4348
|
+
if (token.endsWith(":") && token.length > 1) {
|
|
4349
|
+
const prefix = token.slice(0, -1);
|
|
4350
|
+
const uri = parts[i + 1];
|
|
4351
|
+
if (!uri || uri.endsWith(":")) {
|
|
4352
|
+
pushMessage(context.messages, {
|
|
4353
|
+
id: MessageId.OPF_005,
|
|
4354
|
+
message: `The prefix "${prefix}" is declared but no URI is bound to it.`,
|
|
4355
|
+
location: { path: opfPath }
|
|
4356
|
+
});
|
|
4357
|
+
i += 1;
|
|
4358
|
+
continue;
|
|
4359
|
+
}
|
|
4360
|
+
if (!isValidURI(uri)) {
|
|
4361
|
+
pushMessage(context.messages, {
|
|
4362
|
+
id: MessageId.OPF_006,
|
|
4363
|
+
message: `The value "${uri}" bound to prefix "${prefix}" is not a valid URI.`,
|
|
4364
|
+
location: { path: opfPath }
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4367
|
+
const reservedUri = RESERVED_PREFIX_URIS[prefix];
|
|
4368
|
+
if (reservedUri !== void 0 && reservedUri !== uri) {
|
|
4369
|
+
pushMessage(context.messages, {
|
|
4370
|
+
id: MessageId.OPF_007,
|
|
4371
|
+
message: `The prefix "${prefix}" is reserved and must not be re-declared.`,
|
|
4372
|
+
location: { path: opfPath }
|
|
4373
|
+
});
|
|
4374
|
+
}
|
|
4375
|
+
i += 2;
|
|
4376
|
+
} else {
|
|
4377
|
+
i += 1;
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4270
4381
|
/**
|
|
4271
4382
|
* RSC-005: all id attributes on elements in the OPF document must be unique.
|
|
4272
4383
|
* Mirrors Java's id-unique.sch / opf.sch opf_idAttrUnique pattern, which
|
|
@@ -4275,16 +4386,7 @@ var OPFValidator = class {
|
|
|
4275
4386
|
*/
|
|
4276
4387
|
validateMetaPrefixes(context, opfPath, opfXml) {
|
|
4277
4388
|
if (!this.packageDoc) return;
|
|
4278
|
-
const RESERVED =
|
|
4279
|
-
"dcterms",
|
|
4280
|
-
"marc",
|
|
4281
|
-
"onix",
|
|
4282
|
-
"schema",
|
|
4283
|
-
"xsd",
|
|
4284
|
-
"a11y",
|
|
4285
|
-
"media",
|
|
4286
|
-
"rendition"
|
|
4287
|
-
]);
|
|
4389
|
+
const RESERVED = new Set(Object.keys(RESERVED_PREFIX_URIS));
|
|
4288
4390
|
const declared = new Set(Object.keys(this.packageDoc.prefixes ?? {}));
|
|
4289
4391
|
const reported = /* @__PURE__ */ new Set();
|
|
4290
4392
|
const reportIfUndeclared = (prefix) => {
|
|
@@ -4491,6 +4593,26 @@ var OPFValidator = class {
|
|
|
4491
4593
|
}
|
|
4492
4594
|
}
|
|
4493
4595
|
}
|
|
4596
|
+
validatePageMap(context, opfPath, opfXml) {
|
|
4597
|
+
if (!this.packageDoc) return;
|
|
4598
|
+
const stripped = stripXmlComments(opfXml);
|
|
4599
|
+
const m = /<spine\b[^>]*\spage-map\s*=\s*["']([^"']*)["']/.exec(stripped);
|
|
4600
|
+
if (!m) return;
|
|
4601
|
+
const pageMapId = (m[1] ?? "").trim();
|
|
4602
|
+
pushMessage(context.messages, {
|
|
4603
|
+
id: MessageId.OPF_062,
|
|
4604
|
+
message: `Found Adobe page-map attribute on spine element (page-map="${pageMapId}")`,
|
|
4605
|
+
location: { path: opfPath }
|
|
4606
|
+
});
|
|
4607
|
+
if (!pageMapId) return;
|
|
4608
|
+
if (!this.manifestById.has(pageMapId)) {
|
|
4609
|
+
pushMessage(context.messages, {
|
|
4610
|
+
id: MessageId.OPF_063,
|
|
4611
|
+
message: `The Adobe page-map item "${pageMapId}" was not found in the manifest`,
|
|
4612
|
+
location: { path: opfPath }
|
|
4613
|
+
});
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4494
4616
|
/**
|
|
4495
4617
|
* Validate fallback chains
|
|
4496
4618
|
*/
|
|
@@ -7214,6 +7336,9 @@ var ContentValidator = class {
|
|
|
7214
7336
|
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
7215
7337
|
const opfDir = context.opfPath?.includes("/") ? context.opfPath.substring(0, context.opfPath.lastIndexOf("/")) : "";
|
|
7216
7338
|
const tocAnchors = tocNav.find(".//html:a[@href]", HTML_NS);
|
|
7339
|
+
if (context.contentFeatures) {
|
|
7340
|
+
context.contentFeatures.tocLinkCount = (context.contentFeatures.tocLinkCount ?? 0) + tocAnchors.length;
|
|
7341
|
+
}
|
|
7217
7342
|
const tocLinks = [];
|
|
7218
7343
|
for (const anchor of tocAnchors) {
|
|
7219
7344
|
const href = this.getAttribute(anchor, "href")?.trim();
|
|
@@ -8336,6 +8461,10 @@ var ContentValidator = class {
|
|
|
8336
8461
|
if (!features.hasRDFa && root.get(".//*[@property]")) {
|
|
8337
8462
|
features.hasRDFa = true;
|
|
8338
8463
|
}
|
|
8464
|
+
if (context.options.profile === "edupub") {
|
|
8465
|
+
const sections = root.find(".//html:body//html:section", XHTML_NS);
|
|
8466
|
+
features.sectionCount = (features.sectionCount ?? 0) + sections.length;
|
|
8467
|
+
}
|
|
8339
8468
|
}
|
|
8340
8469
|
validateImages(context, path, root) {
|
|
8341
8470
|
const packageDoc = context.packageDocument;
|
|
@@ -10271,6 +10400,13 @@ var NCXValidator = class {
|
|
|
10271
10400
|
});
|
|
10272
10401
|
return;
|
|
10273
10402
|
}
|
|
10403
|
+
if (uidContent !== uidContent.trim()) {
|
|
10404
|
+
pushMessage(context.messages, {
|
|
10405
|
+
id: MessageId.NCX_004,
|
|
10406
|
+
message: "NCX dtb:uid meta content has leading or trailing whitespace.",
|
|
10407
|
+
location: { path, line: uidElement.line }
|
|
10408
|
+
});
|
|
10409
|
+
}
|
|
10274
10410
|
context.ncxUid = uidContent.trim();
|
|
10275
10411
|
}
|
|
10276
10412
|
checkNavMap(context, root, path) {
|
|
@@ -10900,8 +11036,8 @@ var OCFValidator = class {
|
|
|
10900
11036
|
zip = ZipReader.open(context.data);
|
|
10901
11037
|
} catch (error) {
|
|
10902
11038
|
pushMessage(context.messages, {
|
|
10903
|
-
id: MessageId.
|
|
10904
|
-
message: `Failed to open EPUB
|
|
11039
|
+
id: MessageId.PKG_004,
|
|
11040
|
+
message: `Failed to open EPUB ZIP: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
10905
11041
|
});
|
|
10906
11042
|
return;
|
|
10907
11043
|
}
|
|
@@ -10935,8 +11071,8 @@ var OCFValidator = class {
|
|
|
10935
11071
|
const compressionInfo = zip.getMimetypeCompressionInfo();
|
|
10936
11072
|
if (compressionInfo === null) {
|
|
10937
11073
|
pushMessage(messages, {
|
|
10938
|
-
id: MessageId.
|
|
10939
|
-
message: "
|
|
11074
|
+
id: MessageId.PKG_003,
|
|
11075
|
+
message: "Unable to read EPUB file header, likely corrupted",
|
|
10940
11076
|
location: { path: "mimetype" }
|
|
10941
11077
|
});
|
|
10942
11078
|
return;
|
|
@@ -12201,7 +12337,7 @@ var EpubCheck = class _EpubCheck {
|
|
|
12201
12337
|
await this.runPipeline(context);
|
|
12202
12338
|
} catch (error) {
|
|
12203
12339
|
pushMessage(context.messages, {
|
|
12204
|
-
id: MessageId.
|
|
12340
|
+
id: MessageId.PKG_008,
|
|
12205
12341
|
message: error instanceof Error ? error.message : "Unknown validation error"
|
|
12206
12342
|
});
|
|
12207
12343
|
} finally {
|
|
@@ -12243,7 +12379,7 @@ var EpubCheck = class _EpubCheck {
|
|
|
12243
12379
|
await this.runPipeline(context);
|
|
12244
12380
|
} catch (error) {
|
|
12245
12381
|
pushMessage(context.messages, {
|
|
12246
|
-
id: MessageId.
|
|
12382
|
+
id: MessageId.PKG_008,
|
|
12247
12383
|
message: error instanceof Error ? error.message : "Unknown validation error"
|
|
12248
12384
|
});
|
|
12249
12385
|
} finally {
|
|
@@ -12310,7 +12446,7 @@ var EpubCheck = class _EpubCheck {
|
|
|
12310
12446
|
}
|
|
12311
12447
|
} catch (error) {
|
|
12312
12448
|
pushMessage(context.messages, {
|
|
12313
|
-
id: MessageId.
|
|
12449
|
+
id: MessageId.PKG_008,
|
|
12314
12450
|
message: error instanceof Error ? error.message : "Unknown validation error"
|
|
12315
12451
|
});
|
|
12316
12452
|
} finally {
|
|
@@ -12360,6 +12496,15 @@ var EpubCheck = class _EpubCheck {
|
|
|
12360
12496
|
const profile = context.options.profile;
|
|
12361
12497
|
const opfPath = context.opfPath ?? "";
|
|
12362
12498
|
if (profile === "edupub") {
|
|
12499
|
+
const sectionCount = features.sectionCount ?? 0;
|
|
12500
|
+
const tocLinkCount = features.tocLinkCount ?? 0;
|
|
12501
|
+
if (sectionCount > 0 && sectionCount !== tocLinkCount) {
|
|
12502
|
+
pushMessage(context.messages, {
|
|
12503
|
+
id: MessageId.NAV_004,
|
|
12504
|
+
message: "The Navigation Document should contain the full hierarchy of headings in the document for EDUPUB.",
|
|
12505
|
+
location: { path: opfPath }
|
|
12506
|
+
});
|
|
12507
|
+
}
|
|
12363
12508
|
if (features.hasPageBreak && !features.hasPageList) {
|
|
12364
12509
|
pushMessage(context.messages, {
|
|
12365
12510
|
id: MessageId.NAV_003,
|
|
@@ -12700,7 +12845,14 @@ var EpubCheck = class _EpubCheck {
|
|
|
12700
12845
|
message: "For maximum compatibility, use only lowercase characters for the EPUB file extension.",
|
|
12701
12846
|
location: { path: filename }
|
|
12702
12847
|
});
|
|
12848
|
+
return;
|
|
12703
12849
|
}
|
|
12850
|
+
const isEpub2 = context.version.startsWith("2");
|
|
12851
|
+
pushMessage(context.messages, {
|
|
12852
|
+
id: isEpub2 ? MessageId.PKG_017 : MessageId.PKG_024,
|
|
12853
|
+
message: `EPUB file has an uncommon extension "${extension}".`,
|
|
12854
|
+
location: { path: filename }
|
|
12855
|
+
});
|
|
12704
12856
|
}
|
|
12705
12857
|
/**
|
|
12706
12858
|
* Build a filtered report from validation context
|