@likecoin/epubcheck-ts 0.3.9 → 0.4.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 +36 -23
- package/bin/epubcheck.js +41 -12
- package/bin/epubcheck.ts +44 -14
- package/dist/index.cjs +672 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +437 -400
- package/dist/index.d.ts +437 -400
- package/dist/index.js +672 -44
- package/dist/index.js.map +1 -1
- package/package.json +21 -5
package/dist/index.js
CHANGED
|
@@ -5,6 +5,13 @@ import { unzipSync, strFromU8, gunzipSync } from 'fflate';
|
|
|
5
5
|
// src/content/validator.ts
|
|
6
6
|
|
|
7
7
|
// src/messages/messages.ts
|
|
8
|
+
var severityOverrides = /* @__PURE__ */ new Map();
|
|
9
|
+
function setSeverityOverrides(overrides) {
|
|
10
|
+
severityOverrides = overrides;
|
|
11
|
+
}
|
|
12
|
+
function clearSeverityOverrides() {
|
|
13
|
+
severityOverrides = /* @__PURE__ */ new Map();
|
|
14
|
+
}
|
|
8
15
|
var MessageDefs = {
|
|
9
16
|
// Package/Container errors (PKG-*)
|
|
10
17
|
PKG_001: {
|
|
@@ -1175,10 +1182,15 @@ function formatMessageList() {
|
|
|
1175
1182
|
function createMessage(options) {
|
|
1176
1183
|
const { id, message, location, suggestion, severityOverride } = options;
|
|
1177
1184
|
const registeredSeverity = getDefaultSeverity(id);
|
|
1178
|
-
|
|
1185
|
+
const globalOverride = severityOverrides.get(id);
|
|
1186
|
+
const effectiveOverride = severityOverride ?? globalOverride;
|
|
1187
|
+
if (effectiveOverride === "suppressed") {
|
|
1179
1188
|
return null;
|
|
1180
1189
|
}
|
|
1181
|
-
|
|
1190
|
+
if (registeredSeverity === "suppressed" && !effectiveOverride) {
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
const severity = effectiveOverride ? effectiveOverride : registeredSeverity;
|
|
1182
1194
|
const result = {
|
|
1183
1195
|
id,
|
|
1184
1196
|
severity,
|
|
@@ -1198,6 +1210,30 @@ function pushMessage(messages, options) {
|
|
|
1198
1210
|
messages.push(msg);
|
|
1199
1211
|
}
|
|
1200
1212
|
}
|
|
1213
|
+
function parseCustomMessages(content) {
|
|
1214
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
1215
|
+
const validSeverities = /* @__PURE__ */ new Set([
|
|
1216
|
+
"fatal",
|
|
1217
|
+
"error",
|
|
1218
|
+
"warning",
|
|
1219
|
+
"info",
|
|
1220
|
+
"usage",
|
|
1221
|
+
"suppressed"
|
|
1222
|
+
]);
|
|
1223
|
+
for (const line of content.split("\n")) {
|
|
1224
|
+
const trimmed = line.trim();
|
|
1225
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1226
|
+
if (trimmed.toLowerCase().startsWith("id ") || trimmed.toLowerCase() === "id") continue;
|
|
1227
|
+
const parts = trimmed.split(" ");
|
|
1228
|
+
const id = parts[0]?.trim();
|
|
1229
|
+
const severity = parts[1]?.trim().toLowerCase();
|
|
1230
|
+
if (!id || !severity) continue;
|
|
1231
|
+
if (validSeverities.has(severity)) {
|
|
1232
|
+
overrides.set(id, severity);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return overrides;
|
|
1236
|
+
}
|
|
1201
1237
|
|
|
1202
1238
|
// src/css/validator.ts
|
|
1203
1239
|
var BLESSED_FONT_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -2134,6 +2170,7 @@ var SPECIAL_URL_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "ftp", "ws",
|
|
|
2134
2170
|
var CSS_CHARSET_RE = /^@charset\s+"([^"]+)"\s*;/;
|
|
2135
2171
|
var EPUB_XMLNS_RE = /xmlns:epub\s*=\s*"([^"]*)"/;
|
|
2136
2172
|
var XHTML_NS = { html: "http://www.w3.org/1999/xhtml" };
|
|
2173
|
+
var EPUB_OPS_NS = { epub: "http://www.idpf.org/2007/ops" };
|
|
2137
2174
|
var EPUB_TYPE_FORBIDDEN_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2138
2175
|
"head",
|
|
2139
2176
|
"meta",
|
|
@@ -2436,6 +2473,122 @@ var HTML_ENTITIES = /* @__PURE__ */ new Set([
|
|
|
2436
2473
|
"thorn",
|
|
2437
2474
|
"yuml"
|
|
2438
2475
|
]);
|
|
2476
|
+
var HTML5_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2477
|
+
"a",
|
|
2478
|
+
"abbr",
|
|
2479
|
+
"address",
|
|
2480
|
+
"area",
|
|
2481
|
+
"article",
|
|
2482
|
+
"aside",
|
|
2483
|
+
"audio",
|
|
2484
|
+
"b",
|
|
2485
|
+
"base",
|
|
2486
|
+
"bdi",
|
|
2487
|
+
"bdo",
|
|
2488
|
+
"blockquote",
|
|
2489
|
+
"body",
|
|
2490
|
+
"br",
|
|
2491
|
+
"button",
|
|
2492
|
+
"canvas",
|
|
2493
|
+
"caption",
|
|
2494
|
+
"cite",
|
|
2495
|
+
"code",
|
|
2496
|
+
"col",
|
|
2497
|
+
"colgroup",
|
|
2498
|
+
"data",
|
|
2499
|
+
"datalist",
|
|
2500
|
+
"dd",
|
|
2501
|
+
"del",
|
|
2502
|
+
"details",
|
|
2503
|
+
"dfn",
|
|
2504
|
+
"dialog",
|
|
2505
|
+
"div",
|
|
2506
|
+
"dl",
|
|
2507
|
+
"dt",
|
|
2508
|
+
"em",
|
|
2509
|
+
"embed",
|
|
2510
|
+
"fieldset",
|
|
2511
|
+
"figcaption",
|
|
2512
|
+
"figure",
|
|
2513
|
+
"footer",
|
|
2514
|
+
"form",
|
|
2515
|
+
"h1",
|
|
2516
|
+
"h2",
|
|
2517
|
+
"h3",
|
|
2518
|
+
"h4",
|
|
2519
|
+
"h5",
|
|
2520
|
+
"h6",
|
|
2521
|
+
"head",
|
|
2522
|
+
"header",
|
|
2523
|
+
"hgroup",
|
|
2524
|
+
"hr",
|
|
2525
|
+
"html",
|
|
2526
|
+
"i",
|
|
2527
|
+
"iframe",
|
|
2528
|
+
"img",
|
|
2529
|
+
"input",
|
|
2530
|
+
"ins",
|
|
2531
|
+
"kbd",
|
|
2532
|
+
"label",
|
|
2533
|
+
"legend",
|
|
2534
|
+
"li",
|
|
2535
|
+
"link",
|
|
2536
|
+
"main",
|
|
2537
|
+
"map",
|
|
2538
|
+
"mark",
|
|
2539
|
+
"math",
|
|
2540
|
+
"menu",
|
|
2541
|
+
"meta",
|
|
2542
|
+
"meter",
|
|
2543
|
+
"nav",
|
|
2544
|
+
"noscript",
|
|
2545
|
+
"object",
|
|
2546
|
+
"ol",
|
|
2547
|
+
"optgroup",
|
|
2548
|
+
"option",
|
|
2549
|
+
"output",
|
|
2550
|
+
"p",
|
|
2551
|
+
"picture",
|
|
2552
|
+
"pre",
|
|
2553
|
+
"progress",
|
|
2554
|
+
"q",
|
|
2555
|
+
"rp",
|
|
2556
|
+
"rt",
|
|
2557
|
+
"ruby",
|
|
2558
|
+
"s",
|
|
2559
|
+
"samp",
|
|
2560
|
+
"script",
|
|
2561
|
+
"search",
|
|
2562
|
+
"section",
|
|
2563
|
+
"select",
|
|
2564
|
+
"slot",
|
|
2565
|
+
"small",
|
|
2566
|
+
"source",
|
|
2567
|
+
"span",
|
|
2568
|
+
"strong",
|
|
2569
|
+
"style",
|
|
2570
|
+
"sub",
|
|
2571
|
+
"summary",
|
|
2572
|
+
"sup",
|
|
2573
|
+
"svg",
|
|
2574
|
+
"table",
|
|
2575
|
+
"tbody",
|
|
2576
|
+
"td",
|
|
2577
|
+
"template",
|
|
2578
|
+
"textarea",
|
|
2579
|
+
"tfoot",
|
|
2580
|
+
"th",
|
|
2581
|
+
"thead",
|
|
2582
|
+
"time",
|
|
2583
|
+
"title",
|
|
2584
|
+
"tr",
|
|
2585
|
+
"track",
|
|
2586
|
+
"u",
|
|
2587
|
+
"ul",
|
|
2588
|
+
"var",
|
|
2589
|
+
"video",
|
|
2590
|
+
"wbr"
|
|
2591
|
+
]);
|
|
2439
2592
|
function isItemFixedLayout(packageDoc, itemId) {
|
|
2440
2593
|
const spineItem = packageDoc.spine.find((s) => s.idref === itemId);
|
|
2441
2594
|
if (!spineItem) return false;
|
|
@@ -2463,6 +2616,7 @@ var ContentValidator = class {
|
|
|
2463
2616
|
}
|
|
2464
2617
|
}
|
|
2465
2618
|
}
|
|
2619
|
+
context.contentFeatures = {};
|
|
2466
2620
|
const overlayDocMap = /* @__PURE__ */ new Map();
|
|
2467
2621
|
const manifestByPath = /* @__PURE__ */ new Map();
|
|
2468
2622
|
for (const item of packageDoc.manifest) {
|
|
@@ -2659,6 +2813,8 @@ var ContentValidator = class {
|
|
|
2659
2813
|
this.validateSvgEpubType(context, path, root);
|
|
2660
2814
|
this.checkUnknownEpubAttributes(context, path, root);
|
|
2661
2815
|
this.checkSVGLinkAccessibility(context, path, root);
|
|
2816
|
+
this.checkForeignObjectContent(context, path, root, true);
|
|
2817
|
+
this.checkSVGTitleContent(context, path, root);
|
|
2662
2818
|
const packageDoc = context.packageDocument;
|
|
2663
2819
|
if (packageDoc && isItemFixedLayout(packageDoc, manifestItem.id)) {
|
|
2664
2820
|
const viewBox = this.getAttribute(root, "viewBox");
|
|
@@ -2881,21 +3037,24 @@ var ContentValidator = class {
|
|
|
2881
3037
|
const hasRemoteResources = result.references.some(
|
|
2882
3038
|
(ref) => ref.url.startsWith("http://") || ref.url.startsWith("https://")
|
|
2883
3039
|
);
|
|
3040
|
+
const cssManifestItem = context.packageDocument?.manifest.find(
|
|
3041
|
+
(item) => path.endsWith(`/${item.href}`) || path === item.href
|
|
3042
|
+
);
|
|
2884
3043
|
if (hasRemoteResources) {
|
|
2885
3044
|
this.cssWithRemoteResources.add(path);
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
pushMessage(context.messages, {
|
|
2893
|
-
id: MessageId.OPF_014,
|
|
2894
|
-
message: 'CSS document references remote resources but manifest item is missing "remote-resources" property',
|
|
2895
|
-
location: { path }
|
|
2896
|
-
});
|
|
2897
|
-
}
|
|
3045
|
+
if (cssManifestItem && !cssManifestItem.properties?.includes("remote-resources")) {
|
|
3046
|
+
pushMessage(context.messages, {
|
|
3047
|
+
id: MessageId.OPF_014,
|
|
3048
|
+
message: 'CSS document references remote resources but manifest item is missing "remote-resources" property',
|
|
3049
|
+
location: { path }
|
|
3050
|
+
});
|
|
2898
3051
|
}
|
|
3052
|
+
} else if (cssManifestItem?.properties?.includes("remote-resources")) {
|
|
3053
|
+
pushMessage(context.messages, {
|
|
3054
|
+
id: MessageId.OPF_018,
|
|
3055
|
+
message: 'The "remote-resources" property was declared in the Package Document, but no reference to remote resources has been found',
|
|
3056
|
+
location: { path }
|
|
3057
|
+
});
|
|
2899
3058
|
}
|
|
2900
3059
|
const cssDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
2901
3060
|
for (const ref of result.references) {
|
|
@@ -3204,9 +3363,19 @@ var ContentValidator = class {
|
|
|
3204
3363
|
this.checkAccessibility(context, path, root);
|
|
3205
3364
|
this.validateImages(context, path, root);
|
|
3206
3365
|
this.checkUsemapAttribute(context, path, root);
|
|
3366
|
+
if (context.version.startsWith("3")) {
|
|
3367
|
+
this.checkDisallowedDescendants(context, path, root);
|
|
3368
|
+
this.checkMicrodataCoOccurrence(context, path, root);
|
|
3369
|
+
this.checkUnknownElements(context, path, root);
|
|
3370
|
+
this.checkForeignObjectContent(context, path, root, false);
|
|
3371
|
+
this.checkSVGTitleContent(context, path, root);
|
|
3372
|
+
}
|
|
3207
3373
|
if (context.version.startsWith("3")) {
|
|
3208
3374
|
this.validateEpubTypes(context, path, root);
|
|
3209
3375
|
}
|
|
3376
|
+
if (context.version.startsWith("3")) {
|
|
3377
|
+
this.collectFeatures(context, root);
|
|
3378
|
+
}
|
|
3210
3379
|
this.validateEpubSwitch(context, path, root);
|
|
3211
3380
|
this.validateEpubTrigger(context, path, root);
|
|
3212
3381
|
this.validateStyleAttributes(context, path, root);
|
|
@@ -3364,8 +3533,15 @@ var ContentValidator = class {
|
|
|
3364
3533
|
if (types.includes("toc") && !tocNav) {
|
|
3365
3534
|
tocNav = nav;
|
|
3366
3535
|
}
|
|
3367
|
-
if (types.includes("page-list"))
|
|
3536
|
+
if (types.includes("page-list")) {
|
|
3537
|
+
pageListCount++;
|
|
3538
|
+
if (context.contentFeatures) context.contentFeatures.hasPageList = true;
|
|
3539
|
+
}
|
|
3368
3540
|
if (types.includes("landmarks")) landmarksCount++;
|
|
3541
|
+
if (types.includes("loi") && context.contentFeatures) context.contentFeatures.hasLOI = true;
|
|
3542
|
+
if (types.includes("lot") && context.contentFeatures) context.contentFeatures.hasLOT = true;
|
|
3543
|
+
if (types.includes("loa") && context.contentFeatures) context.contentFeatures.hasLOA = true;
|
|
3544
|
+
if (types.includes("lov") && context.contentFeatures) context.contentFeatures.hasLOV = true;
|
|
3369
3545
|
}
|
|
3370
3546
|
if (!tocNav) {
|
|
3371
3547
|
pushMessage(context.messages, {
|
|
@@ -3405,6 +3581,14 @@ var ContentValidator = class {
|
|
|
3405
3581
|
if (!isStandard) {
|
|
3406
3582
|
this.checkNavFirstChildHeading(context, path, navElem);
|
|
3407
3583
|
}
|
|
3584
|
+
const flatNavType = types.includes("page-list") ? "page-list" : types.includes("landmarks") ? "landmarks" : null;
|
|
3585
|
+
if (flatNavType && navElem.find(".//html:ol", XHTML_NS).length > 1) {
|
|
3586
|
+
pushMessage(context.messages, {
|
|
3587
|
+
id: MessageId.RSC_017,
|
|
3588
|
+
message: `A "${flatNavType}" nav element should contain only a single ol descendant (no nested sublists)`,
|
|
3589
|
+
location: { path }
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3408
3592
|
if (types.includes("landmarks")) {
|
|
3409
3593
|
this.checkNavLandmarks(context, path, navElem);
|
|
3410
3594
|
}
|
|
@@ -4571,7 +4755,7 @@ var ContentValidator = class {
|
|
|
4571
4755
|
const altAttr = this.getAttribute(img, "alt");
|
|
4572
4756
|
if (altAttr === null) {
|
|
4573
4757
|
pushMessage(context.messages, {
|
|
4574
|
-
id: MessageId.
|
|
4758
|
+
id: MessageId.ACC_001,
|
|
4575
4759
|
message: "Image is missing alt attribute",
|
|
4576
4760
|
location: { path }
|
|
4577
4761
|
});
|
|
@@ -4594,6 +4778,53 @@ var ContentValidator = class {
|
|
|
4594
4778
|
});
|
|
4595
4779
|
}
|
|
4596
4780
|
}
|
|
4781
|
+
const tables = root.find(".//html:table", XHTML_NS);
|
|
4782
|
+
for (const table of tables) {
|
|
4783
|
+
const tableElem = table;
|
|
4784
|
+
const thCells = tableElem.find(".//html:th", XHTML_NS);
|
|
4785
|
+
if (thCells.length === 0) {
|
|
4786
|
+
pushMessage(context.messages, {
|
|
4787
|
+
id: MessageId.ACC_005,
|
|
4788
|
+
message: 'Table heading cells should be identified by "th" elements for accessibility',
|
|
4789
|
+
location: { path }
|
|
4790
|
+
});
|
|
4791
|
+
}
|
|
4792
|
+
for (const th of thCells) {
|
|
4793
|
+
if (!th.content.trim()) {
|
|
4794
|
+
pushMessage(context.messages, {
|
|
4795
|
+
id: MessageId.ACC_014,
|
|
4796
|
+
message: "Table header cell is empty",
|
|
4797
|
+
location: { path }
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
if (!tableElem.get(".//html:thead", XHTML_NS)) {
|
|
4802
|
+
pushMessage(context.messages, {
|
|
4803
|
+
id: MessageId.ACC_006,
|
|
4804
|
+
message: 'Tables should include a "thead" element for accessibility',
|
|
4805
|
+
location: { path }
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4808
|
+
if (!tableElem.get("./html:caption", XHTML_NS)) {
|
|
4809
|
+
pushMessage(context.messages, {
|
|
4810
|
+
id: MessageId.ACC_012,
|
|
4811
|
+
message: 'Table elements should include a "caption" element',
|
|
4812
|
+
location: { path }
|
|
4813
|
+
});
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
if (context.packageDocument?.version.startsWith("3.")) {
|
|
4817
|
+
const epubTypeElements = root.find(".//*[@epub:type]", {
|
|
4818
|
+
epub: "http://www.idpf.org/2007/ops"
|
|
4819
|
+
});
|
|
4820
|
+
if (epubTypeElements.length === 0) {
|
|
4821
|
+
pushMessage(context.messages, {
|
|
4822
|
+
id: MessageId.ACC_007,
|
|
4823
|
+
message: 'Content Documents do not use "epub:type" attributes for semantic inflection',
|
|
4824
|
+
location: { path }
|
|
4825
|
+
});
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4597
4828
|
}
|
|
4598
4829
|
hasSVGLinkAccessibleName(svgElem) {
|
|
4599
4830
|
const ns = { svg: "http://www.w3.org/2000/svg" };
|
|
@@ -4618,6 +4849,46 @@ var ContentValidator = class {
|
|
|
4618
4849
|
}
|
|
4619
4850
|
}
|
|
4620
4851
|
}
|
|
4852
|
+
collectFeatures(context, root) {
|
|
4853
|
+
const features = context.contentFeatures;
|
|
4854
|
+
if (!features) return;
|
|
4855
|
+
if (!features.hasTable && root.get(".//html:table", XHTML_NS)) {
|
|
4856
|
+
features.hasTable = true;
|
|
4857
|
+
}
|
|
4858
|
+
if (!features.hasFigure && root.get(".//html:figure", XHTML_NS)) {
|
|
4859
|
+
features.hasFigure = true;
|
|
4860
|
+
}
|
|
4861
|
+
if (!features.hasAudio && root.get(".//html:audio", XHTML_NS)) {
|
|
4862
|
+
features.hasAudio = true;
|
|
4863
|
+
}
|
|
4864
|
+
if (!features.hasVideo && root.get(".//html:video", XHTML_NS)) {
|
|
4865
|
+
features.hasVideo = true;
|
|
4866
|
+
}
|
|
4867
|
+
if (!features.hasPageBreak || !features.hasDictionary || !features.hasIndex) {
|
|
4868
|
+
const epubTypeElements = root.find(".//*[@epub:type]", EPUB_OPS_NS);
|
|
4869
|
+
for (const el of epubTypeElements) {
|
|
4870
|
+
const attr = el.attr("type", "epub");
|
|
4871
|
+
if (!attr?.value) continue;
|
|
4872
|
+
const tokens = attr.value.trim().split(/\s+/);
|
|
4873
|
+
if (!features.hasPageBreak && tokens.includes("pagebreak")) {
|
|
4874
|
+
features.hasPageBreak = true;
|
|
4875
|
+
}
|
|
4876
|
+
if (!features.hasDictionary && tokens.includes("dictionary")) {
|
|
4877
|
+
features.hasDictionary = true;
|
|
4878
|
+
}
|
|
4879
|
+
if (!features.hasIndex && tokens.includes("index")) {
|
|
4880
|
+
features.hasIndex = true;
|
|
4881
|
+
}
|
|
4882
|
+
if (features.hasPageBreak && features.hasDictionary && features.hasIndex) break;
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
if (!features.hasMicrodata && root.get(".//*[@itemscope]")) {
|
|
4886
|
+
features.hasMicrodata = true;
|
|
4887
|
+
}
|
|
4888
|
+
if (!features.hasRDFa && root.get(".//*[@property]")) {
|
|
4889
|
+
features.hasRDFa = true;
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4621
4892
|
validateImages(context, path, root) {
|
|
4622
4893
|
const packageDoc = context.packageDocument;
|
|
4623
4894
|
if (!packageDoc) return;
|
|
@@ -4716,41 +4987,30 @@ var ContentValidator = class {
|
|
|
4716
4987
|
}
|
|
4717
4988
|
validateStylesheetLinks(context, path, root) {
|
|
4718
4989
|
const linkElements = root.find(".//html:link[@rel]", { html: "http://www.w3.org/1999/xhtml" });
|
|
4719
|
-
const stylesheetTitles = /* @__PURE__ */ new Map();
|
|
4720
4990
|
for (const linkElem of linkElements) {
|
|
4721
4991
|
const elem = linkElem;
|
|
4722
4992
|
const relAttr = this.getAttribute(elem, "rel");
|
|
4723
|
-
|
|
4724
|
-
const
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4993
|
+
if (!relAttr) continue;
|
|
4994
|
+
const rels = relAttr.toLowerCase().split(/\s+/);
|
|
4995
|
+
const classAttr = this.getAttribute(elem, "class");
|
|
4996
|
+
if (classAttr) {
|
|
4997
|
+
const classSet = new Set(classAttr.toLowerCase().split(/\s+/));
|
|
4998
|
+
if (classSet.has("vertical") && classSet.has("horizontal") || classSet.has("day") && classSet.has("night")) {
|
|
4999
|
+
pushMessage(context.messages, {
|
|
5000
|
+
id: MessageId.CSS_005,
|
|
5001
|
+
message: `Conflicting Alt Style Tags found in class attribute: "${classAttr}"`,
|
|
5002
|
+
location: { path }
|
|
5003
|
+
});
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
5006
|
+
if (rels.includes("stylesheet") && rels.includes("alternate")) {
|
|
5007
|
+
if (!this.getAttribute(elem, "title")) {
|
|
4731
5008
|
pushMessage(context.messages, {
|
|
4732
5009
|
id: MessageId.CSS_015,
|
|
4733
5010
|
message: "Alternate stylesheet must have a title attribute",
|
|
4734
5011
|
location: { path }
|
|
4735
5012
|
});
|
|
4736
5013
|
}
|
|
4737
|
-
if (titleAttr) {
|
|
4738
|
-
const key = `${titleAttr}:${isAlternate ? "alt" : "persistent"}`;
|
|
4739
|
-
const expectedRel = isAlternate ? "alternate" : "persistent";
|
|
4740
|
-
const existing = stylesheetTitles.get(key);
|
|
4741
|
-
if (existing) {
|
|
4742
|
-
if (!existing.has(expectedRel)) {
|
|
4743
|
-
pushMessage(context.messages, {
|
|
4744
|
-
id: MessageId.CSS_005,
|
|
4745
|
-
message: `Stylesheet with title "${titleAttr}" conflicts with another stylesheet with same title`,
|
|
4746
|
-
location: { path }
|
|
4747
|
-
});
|
|
4748
|
-
}
|
|
4749
|
-
existing.add(expectedRel);
|
|
4750
|
-
} else {
|
|
4751
|
-
stylesheetTitles.set(key, /* @__PURE__ */ new Set([expectedRel]));
|
|
4752
|
-
}
|
|
4753
|
-
}
|
|
4754
5014
|
}
|
|
4755
5015
|
}
|
|
4756
5016
|
}
|
|
@@ -5744,6 +6004,259 @@ var ContentValidator = class {
|
|
|
5744
6004
|
const result = parts.join("/").normalize("NFC");
|
|
5745
6005
|
return fragment ? `${result}#${fragment}` : result;
|
|
5746
6006
|
}
|
|
6007
|
+
// ── Schematron-equivalent checks ──────────────────────────────────────────
|
|
6008
|
+
checkDisallowedDescendants(context, path, root) {
|
|
6009
|
+
const pairsByAncestor = /* @__PURE__ */ new Map([
|
|
6010
|
+
["dfn", ["dfn"]],
|
|
6011
|
+
["form", ["form"]],
|
|
6012
|
+
["progress", ["progress"]],
|
|
6013
|
+
["meter", ["meter"]],
|
|
6014
|
+
["header", ["header", "footer"]],
|
|
6015
|
+
["footer", ["footer", "header"]],
|
|
6016
|
+
["label", ["label"]],
|
|
6017
|
+
["address", ["address", "header", "footer"]],
|
|
6018
|
+
["caption", ["table"]],
|
|
6019
|
+
["audio", ["audio", "video"]],
|
|
6020
|
+
["video", ["video", "audio"]]
|
|
6021
|
+
]);
|
|
6022
|
+
for (const [ancestor, descendants] of pairsByAncestor) {
|
|
6023
|
+
try {
|
|
6024
|
+
if (root.find(`.//html:${ancestor}`, XHTML_NS).length === 0) continue;
|
|
6025
|
+
} catch {
|
|
6026
|
+
continue;
|
|
6027
|
+
}
|
|
6028
|
+
for (const descendant of descendants) {
|
|
6029
|
+
try {
|
|
6030
|
+
const matches = root.find(`.//html:${ancestor}//html:${descendant}`, XHTML_NS);
|
|
6031
|
+
for (const el of matches) {
|
|
6032
|
+
pushMessage(context.messages, {
|
|
6033
|
+
id: MessageId.RSC_005,
|
|
6034
|
+
message: `The ${descendant} element must not appear inside ${ancestor} elements`,
|
|
6035
|
+
location: { path, line: el.line }
|
|
6036
|
+
});
|
|
6037
|
+
}
|
|
6038
|
+
} catch {
|
|
6039
|
+
}
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
const interactiveExprs = [
|
|
6043
|
+
"html:a",
|
|
6044
|
+
"html:audio[@controls]",
|
|
6045
|
+
"html:button",
|
|
6046
|
+
"html:details",
|
|
6047
|
+
"html:embed",
|
|
6048
|
+
"html:iframe",
|
|
6049
|
+
"html:img[@usemap]",
|
|
6050
|
+
"html:input[not(@type='hidden')]",
|
|
6051
|
+
"html:label",
|
|
6052
|
+
"html:select",
|
|
6053
|
+
"html:textarea",
|
|
6054
|
+
"html:video[@controls]"
|
|
6055
|
+
];
|
|
6056
|
+
for (const ancestor of ["a", "button"]) {
|
|
6057
|
+
try {
|
|
6058
|
+
if (root.find(`.//html:${ancestor}`, XHTML_NS).length === 0) continue;
|
|
6059
|
+
} catch {
|
|
6060
|
+
continue;
|
|
6061
|
+
}
|
|
6062
|
+
for (const expr of interactiveExprs) {
|
|
6063
|
+
try {
|
|
6064
|
+
const matches = root.find(`.//html:${ancestor}//${expr}`, XHTML_NS);
|
|
6065
|
+
for (const el of matches) {
|
|
6066
|
+
const xmlEl = el;
|
|
6067
|
+
const localName = xmlEl.name.includes(":") ? xmlEl.name.substring(xmlEl.name.indexOf(":") + 1) : xmlEl.name;
|
|
6068
|
+
pushMessage(context.messages, {
|
|
6069
|
+
id: MessageId.RSC_005,
|
|
6070
|
+
message: `The ${localName} element must not appear inside ${ancestor} elements`,
|
|
6071
|
+
location: { path, line: el.line }
|
|
6072
|
+
});
|
|
6073
|
+
}
|
|
6074
|
+
} catch {
|
|
6075
|
+
}
|
|
6076
|
+
}
|
|
6077
|
+
}
|
|
6078
|
+
try {
|
|
6079
|
+
const bdos = root.find(".//html:bdo[not(@dir)]", XHTML_NS);
|
|
6080
|
+
for (const el of bdos) {
|
|
6081
|
+
pushMessage(context.messages, {
|
|
6082
|
+
id: MessageId.RSC_005,
|
|
6083
|
+
message: "The bdo element must have a dir attribute",
|
|
6084
|
+
location: { path, line: el.line }
|
|
6085
|
+
});
|
|
6086
|
+
}
|
|
6087
|
+
} catch {
|
|
6088
|
+
}
|
|
6089
|
+
try {
|
|
6090
|
+
const maps = root.find(".//html:map[@id and @name]", XHTML_NS);
|
|
6091
|
+
for (const el of maps) {
|
|
6092
|
+
const id = this.getAttribute(el, "id");
|
|
6093
|
+
const name = this.getAttribute(el, "name");
|
|
6094
|
+
if (id && name && id !== name) {
|
|
6095
|
+
pushMessage(context.messages, {
|
|
6096
|
+
id: MessageId.RSC_005,
|
|
6097
|
+
message: "The id attribute on the map element must have the same value as the name attribute",
|
|
6098
|
+
location: { path, line: el.line }
|
|
6099
|
+
});
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
} catch {
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
6105
|
+
checkMicrodataCoOccurrence(context, path, root) {
|
|
6106
|
+
try {
|
|
6107
|
+
const els = root.find(
|
|
6108
|
+
".//html:a[@itemprop and not(@href)] | .//html:area[@itemprop and not(@href)]",
|
|
6109
|
+
XHTML_NS
|
|
6110
|
+
);
|
|
6111
|
+
for (const el of els) {
|
|
6112
|
+
pushMessage(context.messages, {
|
|
6113
|
+
id: MessageId.RSC_005,
|
|
6114
|
+
message: "If the itemprop is specified on an a element, then the href attribute must also be specified",
|
|
6115
|
+
location: { path, line: el.line }
|
|
6116
|
+
});
|
|
6117
|
+
}
|
|
6118
|
+
} catch {
|
|
6119
|
+
}
|
|
6120
|
+
try {
|
|
6121
|
+
const els = root.find(
|
|
6122
|
+
".//html:iframe[@itemprop and not(@data)] | .//html:embed[@itemprop and not(@data)] | .//html:object[@itemprop and not(@data)]",
|
|
6123
|
+
XHTML_NS
|
|
6124
|
+
);
|
|
6125
|
+
for (const el of els) {
|
|
6126
|
+
pushMessage(context.messages, {
|
|
6127
|
+
id: MessageId.RSC_005,
|
|
6128
|
+
message: "If the itemprop is specified on an iframe, embed or object element, then the data attribute must also be specified",
|
|
6129
|
+
location: { path, line: el.line }
|
|
6130
|
+
});
|
|
6131
|
+
}
|
|
6132
|
+
} catch {
|
|
6133
|
+
}
|
|
6134
|
+
try {
|
|
6135
|
+
const els = root.find(
|
|
6136
|
+
".//html:audio[@itemprop and not(@src)] | .//html:video[@itemprop and not(@src)]",
|
|
6137
|
+
XHTML_NS
|
|
6138
|
+
);
|
|
6139
|
+
for (const el of els) {
|
|
6140
|
+
pushMessage(context.messages, {
|
|
6141
|
+
id: MessageId.RSC_005,
|
|
6142
|
+
message: "If the itemprop is specified on an video or audio element, then the src attribute must also be specified",
|
|
6143
|
+
location: { path, line: el.line }
|
|
6144
|
+
});
|
|
6145
|
+
}
|
|
6146
|
+
} catch {
|
|
6147
|
+
}
|
|
6148
|
+
}
|
|
6149
|
+
checkUnknownElements(context, path, root) {
|
|
6150
|
+
const XHTML_NS2 = "http://www.w3.org/1999/xhtml";
|
|
6151
|
+
try {
|
|
6152
|
+
const allElements = root.find(".//*");
|
|
6153
|
+
for (const el of allElements) {
|
|
6154
|
+
const xmlEl = el;
|
|
6155
|
+
const ns = xmlEl.namespaceUri;
|
|
6156
|
+
if (ns !== XHTML_NS2) continue;
|
|
6157
|
+
const localName = xmlEl.name.includes(":") ? xmlEl.name.substring(xmlEl.name.indexOf(":") + 1) : xmlEl.name;
|
|
6158
|
+
if (localName.includes("-")) continue;
|
|
6159
|
+
if (!HTML5_ELEMENTS.has(localName)) {
|
|
6160
|
+
pushMessage(context.messages, {
|
|
6161
|
+
id: MessageId.RSC_005,
|
|
6162
|
+
message: `element "${localName}" not allowed here`,
|
|
6163
|
+
location: { path, line: el.line }
|
|
6164
|
+
});
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
} catch {
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
6170
|
+
checkForeignObjectContent(context, path, root, isSVGDoc) {
|
|
6171
|
+
const SVG_NS = { svg: "http://www.w3.org/2000/svg" };
|
|
6172
|
+
const XHTML_URI = "http://www.w3.org/1999/xhtml";
|
|
6173
|
+
const DISALLOWED_FO_CHILDREN = /* @__PURE__ */ new Set(["body", "head", "html", "title"]);
|
|
6174
|
+
let foreignObjects;
|
|
6175
|
+
try {
|
|
6176
|
+
foreignObjects = root.find(".//svg:foreignObject", SVG_NS);
|
|
6177
|
+
} catch {
|
|
6178
|
+
return;
|
|
6179
|
+
}
|
|
6180
|
+
for (const fo of foreignObjects) {
|
|
6181
|
+
const foEl = fo;
|
|
6182
|
+
let children;
|
|
6183
|
+
try {
|
|
6184
|
+
children = foEl.find("./*");
|
|
6185
|
+
} catch {
|
|
6186
|
+
continue;
|
|
6187
|
+
}
|
|
6188
|
+
let bodyCount = 0;
|
|
6189
|
+
for (const child of children) {
|
|
6190
|
+
const childEl = child;
|
|
6191
|
+
const childNs = childEl.namespaceUri;
|
|
6192
|
+
const childLocal = childEl.name.includes(":") ? childEl.name.substring(childEl.name.indexOf(":") + 1) : childEl.name;
|
|
6193
|
+
if (isSVGDoc) {
|
|
6194
|
+
if (childNs !== XHTML_URI) {
|
|
6195
|
+
pushMessage(context.messages, {
|
|
6196
|
+
id: MessageId.RSC_005,
|
|
6197
|
+
message: `element "${childLocal}" not allowed here`,
|
|
6198
|
+
location: { path, line: child.line }
|
|
6199
|
+
});
|
|
6200
|
+
continue;
|
|
6201
|
+
}
|
|
6202
|
+
if (childLocal === "body") {
|
|
6203
|
+
bodyCount++;
|
|
6204
|
+
if (bodyCount > 1) {
|
|
6205
|
+
pushMessage(context.messages, {
|
|
6206
|
+
id: MessageId.RSC_005,
|
|
6207
|
+
message: 'element "body" not allowed here',
|
|
6208
|
+
location: { path, line: child.line }
|
|
6209
|
+
});
|
|
6210
|
+
}
|
|
6211
|
+
} else if (childLocal === "title" || childLocal === "head" || childLocal === "html") {
|
|
6212
|
+
pushMessage(context.messages, {
|
|
6213
|
+
id: MessageId.RSC_005,
|
|
6214
|
+
message: `element "${childLocal}" not allowed here`,
|
|
6215
|
+
location: { path, line: child.line }
|
|
6216
|
+
});
|
|
6217
|
+
}
|
|
6218
|
+
} else if (childNs === XHTML_URI && DISALLOWED_FO_CHILDREN.has(childLocal)) {
|
|
6219
|
+
pushMessage(context.messages, {
|
|
6220
|
+
id: MessageId.RSC_005,
|
|
6221
|
+
message: `element "${childLocal}" not allowed here`,
|
|
6222
|
+
location: { path, line: child.line }
|
|
6223
|
+
});
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
checkSVGTitleContent(context, path, root) {
|
|
6229
|
+
const SVG_NS = { svg: "http://www.w3.org/2000/svg" };
|
|
6230
|
+
const XHTML_URI = "http://www.w3.org/1999/xhtml";
|
|
6231
|
+
let svgTitles;
|
|
6232
|
+
try {
|
|
6233
|
+
svgTitles = root.find(".//svg:title", SVG_NS);
|
|
6234
|
+
} catch {
|
|
6235
|
+
return;
|
|
6236
|
+
}
|
|
6237
|
+
for (const titleNode of svgTitles) {
|
|
6238
|
+
const titleEl = titleNode;
|
|
6239
|
+
let descendants;
|
|
6240
|
+
try {
|
|
6241
|
+
descendants = titleEl.find(".//*");
|
|
6242
|
+
} catch {
|
|
6243
|
+
continue;
|
|
6244
|
+
}
|
|
6245
|
+
const reportedNamespaces = /* @__PURE__ */ new Set();
|
|
6246
|
+
for (const desc of descendants) {
|
|
6247
|
+
const descEl = desc;
|
|
6248
|
+
const descNs = descEl.namespaceUri;
|
|
6249
|
+
if (descNs && descNs !== XHTML_URI && !reportedNamespaces.has(descNs)) {
|
|
6250
|
+
reportedNamespaces.add(descNs);
|
|
6251
|
+
pushMessage(context.messages, {
|
|
6252
|
+
id: MessageId.RSC_005,
|
|
6253
|
+
message: `elements from namespace "${descNs}" are not allowed`,
|
|
6254
|
+
location: { path, line: desc.line }
|
|
6255
|
+
});
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
5747
6260
|
};
|
|
5748
6261
|
|
|
5749
6262
|
// src/core/report.ts
|
|
@@ -7540,6 +8053,9 @@ var OPFValidator = class {
|
|
|
7540
8053
|
}
|
|
7541
8054
|
}
|
|
7542
8055
|
}
|
|
8056
|
+
if (this.packageDoc.version.startsWith("3.")) {
|
|
8057
|
+
this.validateAccessibilityMetadata(context, opfPath);
|
|
8058
|
+
}
|
|
7543
8059
|
}
|
|
7544
8060
|
/**
|
|
7545
8061
|
* Build lookup maps for manifest items
|
|
@@ -7589,6 +8105,41 @@ var OPFValidator = class {
|
|
|
7589
8105
|
}
|
|
7590
8106
|
}
|
|
7591
8107
|
}
|
|
8108
|
+
/**
|
|
8109
|
+
* Validate accessibility metadata (ACC-002, ACC-003, ACC-010)
|
|
8110
|
+
*/
|
|
8111
|
+
validateAccessibilityMetadata(context, opfPath) {
|
|
8112
|
+
if (!this.packageDoc) return;
|
|
8113
|
+
const metaElements = this.packageDoc.metaElements;
|
|
8114
|
+
const a11yProperties = [
|
|
8115
|
+
"schema:accessMode",
|
|
8116
|
+
"schema:accessibilityFeature",
|
|
8117
|
+
"schema:accessibilityHazard",
|
|
8118
|
+
"schema:accessibilitySummary"
|
|
8119
|
+
];
|
|
8120
|
+
const hasAnyA11y = metaElements.some((m) => a11yProperties.includes(m.property));
|
|
8121
|
+
if (!hasAnyA11y) {
|
|
8122
|
+
pushMessage(context.messages, {
|
|
8123
|
+
id: MessageId.ACC_003,
|
|
8124
|
+
message: "Publication does not include any accessibility metadata",
|
|
8125
|
+
location: { path: opfPath }
|
|
8126
|
+
});
|
|
8127
|
+
}
|
|
8128
|
+
if (!metaElements.some((m) => m.property === "schema:accessibilityFeature")) {
|
|
8129
|
+
pushMessage(context.messages, {
|
|
8130
|
+
id: MessageId.ACC_002,
|
|
8131
|
+
message: 'Missing "schema:accessibilityFeature" metadata',
|
|
8132
|
+
location: { path: opfPath }
|
|
8133
|
+
});
|
|
8134
|
+
}
|
|
8135
|
+
if (!metaElements.some((m) => m.property === "schema:accessMode")) {
|
|
8136
|
+
pushMessage(context.messages, {
|
|
8137
|
+
id: MessageId.ACC_010,
|
|
8138
|
+
message: 'Missing "schema:accessMode" metadata',
|
|
8139
|
+
location: { path: opfPath }
|
|
8140
|
+
});
|
|
8141
|
+
}
|
|
8142
|
+
}
|
|
7592
8143
|
/**
|
|
7593
8144
|
* Validate metadata section
|
|
7594
8145
|
*/
|
|
@@ -9955,7 +10506,8 @@ var DEFAULT_OPTIONS = {
|
|
|
9955
10506
|
includeUsage: false,
|
|
9956
10507
|
includeInfo: true,
|
|
9957
10508
|
maxErrors: 0,
|
|
9958
|
-
locale: "en"
|
|
10509
|
+
locale: "en",
|
|
10510
|
+
customMessages: /* @__PURE__ */ new Map()
|
|
9959
10511
|
};
|
|
9960
10512
|
var EpubCheck = class _EpubCheck {
|
|
9961
10513
|
options;
|
|
@@ -9981,6 +10533,9 @@ var EpubCheck = class _EpubCheck {
|
|
|
9981
10533
|
files: /* @__PURE__ */ new Map(),
|
|
9982
10534
|
rootfiles: []
|
|
9983
10535
|
};
|
|
10536
|
+
if (this.options.customMessages.size > 0) {
|
|
10537
|
+
setSeverityOverrides(this.options.customMessages);
|
|
10538
|
+
}
|
|
9984
10539
|
try {
|
|
9985
10540
|
const ocfValidator = new OCFValidator();
|
|
9986
10541
|
ocfValidator.validate(context);
|
|
@@ -9998,6 +10553,7 @@ var EpubCheck = class _EpubCheck {
|
|
|
9998
10553
|
}
|
|
9999
10554
|
const contentValidator = new ContentValidator();
|
|
10000
10555
|
contentValidator.validate(context, registry, refValidator);
|
|
10556
|
+
this.validateCrossDocumentFeatures(context);
|
|
10001
10557
|
if (context.packageDocument) {
|
|
10002
10558
|
this.validateNCX(context, registry);
|
|
10003
10559
|
}
|
|
@@ -10009,6 +10565,8 @@ var EpubCheck = class _EpubCheck {
|
|
|
10009
10565
|
id: MessageId.PKG_025,
|
|
10010
10566
|
message: error instanceof Error ? error.message : "Unknown validation error"
|
|
10011
10567
|
});
|
|
10568
|
+
} finally {
|
|
10569
|
+
clearSeverityOverrides();
|
|
10012
10570
|
}
|
|
10013
10571
|
const elapsedMs = performance.now() - startTime;
|
|
10014
10572
|
const filteredMessages = context.messages.filter((msg) => {
|
|
@@ -10039,6 +10597,76 @@ var EpubCheck = class _EpubCheck {
|
|
|
10039
10597
|
get version() {
|
|
10040
10598
|
return this.options.version;
|
|
10041
10599
|
}
|
|
10600
|
+
/**
|
|
10601
|
+
* Cross-document feature validation (Pattern B from Java EPUBCheck)
|
|
10602
|
+
*/
|
|
10603
|
+
validateCrossDocumentFeatures(context) {
|
|
10604
|
+
const features = context.contentFeatures;
|
|
10605
|
+
if (!features || !context.version.startsWith("3")) return;
|
|
10606
|
+
const profile = context.options.profile;
|
|
10607
|
+
const opfPath = context.opfPath ?? "";
|
|
10608
|
+
if (profile === "edupub") {
|
|
10609
|
+
if (features.hasPageBreak && !features.hasPageList) {
|
|
10610
|
+
pushMessage(context.messages, {
|
|
10611
|
+
id: MessageId.NAV_003,
|
|
10612
|
+
message: 'The Navigation Document must have a page list when content contains page breaks (epub:type="pagebreak")',
|
|
10613
|
+
location: { path: opfPath }
|
|
10614
|
+
});
|
|
10615
|
+
}
|
|
10616
|
+
if (features.hasAudio && !features.hasLOA) {
|
|
10617
|
+
pushMessage(context.messages, {
|
|
10618
|
+
id: MessageId.NAV_005,
|
|
10619
|
+
message: 'Content documents contain "audio" elements but the Navigation Document does not have a listing of audio clips (epub:type="loa")',
|
|
10620
|
+
location: { path: opfPath }
|
|
10621
|
+
});
|
|
10622
|
+
}
|
|
10623
|
+
if (features.hasFigure && !features.hasLOI) {
|
|
10624
|
+
pushMessage(context.messages, {
|
|
10625
|
+
id: MessageId.NAV_006,
|
|
10626
|
+
message: 'Content documents contain "figure" elements but the Navigation Document does not have a listing of figures (epub:type="loi")',
|
|
10627
|
+
location: { path: opfPath }
|
|
10628
|
+
});
|
|
10629
|
+
}
|
|
10630
|
+
if (features.hasTable && !features.hasLOT) {
|
|
10631
|
+
pushMessage(context.messages, {
|
|
10632
|
+
id: MessageId.NAV_007,
|
|
10633
|
+
message: 'Content documents contain "table" elements but the Navigation Document does not have a listing of tables (epub:type="lot")',
|
|
10634
|
+
location: { path: opfPath }
|
|
10635
|
+
});
|
|
10636
|
+
}
|
|
10637
|
+
if (features.hasVideo && !features.hasLOV) {
|
|
10638
|
+
pushMessage(context.messages, {
|
|
10639
|
+
id: MessageId.NAV_008,
|
|
10640
|
+
message: 'Content documents contain "video" elements but the Navigation Document does not have a listing of video clips (epub:type="lov")',
|
|
10641
|
+
location: { path: opfPath }
|
|
10642
|
+
});
|
|
10643
|
+
}
|
|
10644
|
+
if (features.hasMicrodata && !features.hasRDFa) {
|
|
10645
|
+
pushMessage(context.messages, {
|
|
10646
|
+
id: MessageId.HTM_051,
|
|
10647
|
+
message: "Found Microdata but no RDFa; EDUPUB recommends the use of RDFa Lite",
|
|
10648
|
+
location: { path: opfPath }
|
|
10649
|
+
});
|
|
10650
|
+
}
|
|
10651
|
+
}
|
|
10652
|
+
const hasDictType = context.packageDocument?.dcElements.some(
|
|
10653
|
+
(dc) => dc.name === "type" && dc.value === "dictionary"
|
|
10654
|
+
) ?? false;
|
|
10655
|
+
if (features.hasDictionary && !hasDictType) {
|
|
10656
|
+
pushMessage(context.messages, {
|
|
10657
|
+
id: MessageId.OPF_079,
|
|
10658
|
+
message: 'Dictionary content was found (epub:type "dictionary"), the Package Document should declare the dc:type "dictionary"',
|
|
10659
|
+
location: { path: opfPath }
|
|
10660
|
+
});
|
|
10661
|
+
}
|
|
10662
|
+
if (profile === "dict" && hasDictType && !features.hasDictionary) {
|
|
10663
|
+
pushMessage(context.messages, {
|
|
10664
|
+
id: MessageId.OPF_078,
|
|
10665
|
+
message: 'An EPUB Dictionary must contain at least one Content Document with dictionary content (epub:type "dictionary")',
|
|
10666
|
+
location: { path: opfPath }
|
|
10667
|
+
});
|
|
10668
|
+
}
|
|
10669
|
+
}
|
|
10042
10670
|
/**
|
|
10043
10671
|
* Validate NCX navigation document (EPUB 2 always, EPUB 3 when NCX present)
|
|
10044
10672
|
*/
|
|
@@ -10169,6 +10797,6 @@ var EpubCheck = class _EpubCheck {
|
|
|
10169
10797
|
}
|
|
10170
10798
|
};
|
|
10171
10799
|
|
|
10172
|
-
export { EpubCheck, MessageId, buildReport, countBySeverity, createMessage, filterByPath, filterBySeverity, formatMessageList, formatMessages, getAllMessages, getDefaultSeverity, getMessageInfo, pushMessage, toJSONReport };
|
|
10800
|
+
export { EpubCheck, MessageId, buildReport, countBySeverity, createMessage, filterByPath, filterBySeverity, formatMessageList, formatMessages, getAllMessages, getDefaultSeverity, getMessageInfo, parseCustomMessages, pushMessage, toJSONReport };
|
|
10173
10801
|
//# sourceMappingURL=index.js.map
|
|
10174
10802
|
//# sourceMappingURL=index.js.map
|