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