@likecoin/epubcheck-ts 0.2.3 → 0.2.4
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 +8 -8
- package/bin/epubcheck.js +4 -4
- package/bin/epubcheck.ts +4 -4
- package/dist/index.cjs +265 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +265 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/schemas/applications.rng +0 -429
- package/schemas/aria.rng +0 -3355
- package/schemas/block.rng +0 -488
- package/schemas/common.rng +0 -1076
- package/schemas/container.rng +0 -24
- package/schemas/core-scripting.rng +0 -950
- package/schemas/data.rng +0 -161
- package/schemas/datatypes.rng +0 -401
- package/schemas/embed.rng +0 -980
- package/schemas/epub-mathml3-inc.rng +0 -161
- package/schemas/epub-nav-30.rnc +0 -44
- package/schemas/epub-nav-30.rng +0 -19985
- package/schemas/epub-nav-30.sch +0 -87
- package/schemas/epub-prefix-attr.rng +0 -17
- package/schemas/epub-shared-inc.rng +0 -29
- package/schemas/epub-ssml-attrs.rng +0 -17
- package/schemas/epub-svg-30.rnc +0 -17
- package/schemas/epub-svg-30.rng +0 -19903
- package/schemas/epub-svg-30.sch +0 -7
- package/schemas/epub-svg-forgiving-inc.rng +0 -315
- package/schemas/epub-switch.rng +0 -121
- package/schemas/epub-trigger.rng +0 -90
- package/schemas/epub-type-attr.rng +0 -12
- package/schemas/epub-xhtml-30.rnc +0 -6
- package/schemas/epub-xhtml-30.rng +0 -19882
- package/schemas/epub-xhtml-30.sch +0 -409
- package/schemas/epub-xhtml-inc.rng +0 -151
- package/schemas/epub-xhtml-integration.rng +0 -565
- package/schemas/epub-xhtml-svg-mathml.rng +0 -17
- package/schemas/form-datatypes.rng +0 -54
- package/schemas/mathml3-common.rng +0 -336
- package/schemas/mathml3-content.rng +0 -1552
- package/schemas/mathml3-inc.rng +0 -30
- package/schemas/mathml3-presentation.rng +0 -2341
- package/schemas/mathml3-strict-content.rng +0 -205
- package/schemas/media.rng +0 -374
- package/schemas/meta.rng +0 -754
- package/schemas/microdata.rng +0 -192
- package/schemas/ncx.rng +0 -308
- package/schemas/ocf-container-30.rnc +0 -37
- package/schemas/ocf-container-30.rng +0 -568
- package/schemas/opf.rng +0 -15
- package/schemas/opf20.rng +0 -513
- package/schemas/package-30.rnc +0 -133
- package/schemas/package-30.rng +0 -1153
- package/schemas/package-30.sch +0 -444
- package/schemas/phrase.rng +0 -746
- package/schemas/rdfa.rng +0 -552
- package/schemas/revision.rng +0 -106
- package/schemas/ruby.rng +0 -141
- package/schemas/sectional.rng +0 -278
- package/schemas/structural.rng +0 -298
- package/schemas/tables.rng +0 -420
- package/schemas/web-components.rng +0 -184
- package/schemas/web-forms.rng +0 -975
- package/schemas/web-forms2.rng +0 -1236
package/dist/index.d.cts
CHANGED
|
@@ -378,6 +378,7 @@ declare enum MessageId {
|
|
|
378
378
|
OPF_013 = "OPF-013",// Remote resource not allowed
|
|
379
379
|
OPF_014 = "OPF-014",// Invalid manifest item media-type
|
|
380
380
|
OPF_097 = "OPF-097",// Resource not referenced
|
|
381
|
+
OPF_099 = "OPF-099",// Manifest must not list the package document
|
|
381
382
|
OPF_015 = "OPF-015",// Invalid guide reference
|
|
382
383
|
RSC_001 = "RSC-001",// Could not open resource
|
|
383
384
|
RSC_002 = "RSC-002",// Resource missing from manifest
|
package/dist/index.d.ts
CHANGED
|
@@ -378,6 +378,7 @@ declare enum MessageId {
|
|
|
378
378
|
OPF_013 = "OPF-013",// Remote resource not allowed
|
|
379
379
|
OPF_014 = "OPF-014",// Invalid manifest item media-type
|
|
380
380
|
OPF_097 = "OPF-097",// Resource not referenced
|
|
381
|
+
OPF_099 = "OPF-099",// Manifest must not list the package document
|
|
381
382
|
OPF_015 = "OPF-015",// Invalid guide reference
|
|
382
383
|
RSC_001 = "RSC-001",// Could not open resource
|
|
383
384
|
RSC_002 = "RSC-002",// Resource missing from manifest
|
package/dist/index.js
CHANGED
|
@@ -62,8 +62,61 @@ var CSSValidator = class {
|
|
|
62
62
|
this.checkDiscouragedProperties(context, ast, resourcePath);
|
|
63
63
|
this.checkAtRules(context, ast, resourcePath, result);
|
|
64
64
|
this.checkMediaOverlayClasses(context, ast, resourcePath);
|
|
65
|
+
this.extractUrlReferences(context, ast, resourcePath, result);
|
|
65
66
|
return result;
|
|
66
67
|
}
|
|
68
|
+
extractUrlReferences(context, ast, resourcePath, result) {
|
|
69
|
+
walk(ast, (node) => {
|
|
70
|
+
if (node.type === "Atrule") {
|
|
71
|
+
const atRule = node;
|
|
72
|
+
if (atRule.name === "font-face") {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (atRule.block) {
|
|
76
|
+
walk(atRule.block, (blockNode) => {
|
|
77
|
+
if (blockNode.type === "Declaration") {
|
|
78
|
+
this.processDeclarationForUrl(blockNode, resourcePath, result);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
} else if (node.type === "Rule") {
|
|
83
|
+
const rule = node;
|
|
84
|
+
walk(rule.block, (blockNode) => {
|
|
85
|
+
if (blockNode.type === "Declaration") {
|
|
86
|
+
this.processDeclarationForUrl(blockNode, resourcePath, result);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
processDeclarationForUrl(declaration, resourcePath, result) {
|
|
93
|
+
const property = declaration.property.toLowerCase();
|
|
94
|
+
walk(declaration.value, (valueNode) => {
|
|
95
|
+
if (valueNode.type === "Url") {
|
|
96
|
+
const urlValue = this.extractUrlValue(valueNode);
|
|
97
|
+
if (urlValue && !urlValue.startsWith("data:")) {
|
|
98
|
+
const loc = valueNode.loc;
|
|
99
|
+
const start = loc?.start;
|
|
100
|
+
if (start) {
|
|
101
|
+
start.line;
|
|
102
|
+
start.column;
|
|
103
|
+
}
|
|
104
|
+
let refType = "resource";
|
|
105
|
+
if (property.includes("font")) {
|
|
106
|
+
refType = "font";
|
|
107
|
+
} else if (property.includes("background") || property.includes("list-style") || property.includes("content") || property.includes("border-image") || property.includes("mask")) {
|
|
108
|
+
refType = "image";
|
|
109
|
+
}
|
|
110
|
+
result.references.push({
|
|
111
|
+
url: urlValue,
|
|
112
|
+
type: refType,
|
|
113
|
+
line: start?.line,
|
|
114
|
+
column: start?.column
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
67
120
|
/**
|
|
68
121
|
* Check for forbidden and discouraged CSS properties in EPUB
|
|
69
122
|
*/
|
|
@@ -432,6 +485,104 @@ function isPublicationResourceReference(type) {
|
|
|
432
485
|
|
|
433
486
|
// src/content/validator.ts
|
|
434
487
|
var DISCOURAGED_ELEMENTS = /* @__PURE__ */ new Set(["base", "embed"]);
|
|
488
|
+
var HTML_ENTITIES = /* @__PURE__ */ new Set([
|
|
489
|
+
"nbsp",
|
|
490
|
+
"iexcl",
|
|
491
|
+
"cent",
|
|
492
|
+
"pound",
|
|
493
|
+
"curren",
|
|
494
|
+
"yen",
|
|
495
|
+
"brvbar",
|
|
496
|
+
"sect",
|
|
497
|
+
"uml",
|
|
498
|
+
"copy",
|
|
499
|
+
"ordf",
|
|
500
|
+
"laquo",
|
|
501
|
+
"not",
|
|
502
|
+
"shy",
|
|
503
|
+
"reg",
|
|
504
|
+
"macr",
|
|
505
|
+
"deg",
|
|
506
|
+
"plusmn",
|
|
507
|
+
"sup2",
|
|
508
|
+
"sup3",
|
|
509
|
+
"acute",
|
|
510
|
+
"micro",
|
|
511
|
+
"para",
|
|
512
|
+
"middot",
|
|
513
|
+
"cedil",
|
|
514
|
+
"sup1",
|
|
515
|
+
"ordm",
|
|
516
|
+
"raquo",
|
|
517
|
+
"frac14",
|
|
518
|
+
"frac12",
|
|
519
|
+
"frac34",
|
|
520
|
+
"iquest",
|
|
521
|
+
"Agrave",
|
|
522
|
+
"Aacute",
|
|
523
|
+
"Acirc",
|
|
524
|
+
"Atilde",
|
|
525
|
+
"Auml",
|
|
526
|
+
"Aring",
|
|
527
|
+
"AElig",
|
|
528
|
+
"Ccedil",
|
|
529
|
+
"Egrave",
|
|
530
|
+
"Eacute",
|
|
531
|
+
"Ecirc",
|
|
532
|
+
"Euml",
|
|
533
|
+
"Igrave",
|
|
534
|
+
"Iacute",
|
|
535
|
+
"Icirc",
|
|
536
|
+
"Iuml",
|
|
537
|
+
"ETH",
|
|
538
|
+
"Ntilde",
|
|
539
|
+
"Ograve",
|
|
540
|
+
"Oacute",
|
|
541
|
+
"Ocirc",
|
|
542
|
+
"Otilde",
|
|
543
|
+
"Ouml",
|
|
544
|
+
"times",
|
|
545
|
+
"Oslash",
|
|
546
|
+
"Ugrave",
|
|
547
|
+
"Uacute",
|
|
548
|
+
"Ucirc",
|
|
549
|
+
"Uuml",
|
|
550
|
+
"Yacute",
|
|
551
|
+
"THORN",
|
|
552
|
+
"szlig",
|
|
553
|
+
"agrave",
|
|
554
|
+
"aacute",
|
|
555
|
+
"acirc",
|
|
556
|
+
"atilde",
|
|
557
|
+
"auml",
|
|
558
|
+
"aring",
|
|
559
|
+
"aelig",
|
|
560
|
+
"ccedil",
|
|
561
|
+
"egrave",
|
|
562
|
+
"eacute",
|
|
563
|
+
"ecirc",
|
|
564
|
+
"euml",
|
|
565
|
+
"igrave",
|
|
566
|
+
"iacute",
|
|
567
|
+
"icirc",
|
|
568
|
+
"iuml",
|
|
569
|
+
"eth",
|
|
570
|
+
"ntilde",
|
|
571
|
+
"ograve",
|
|
572
|
+
"oacute",
|
|
573
|
+
"ocirc",
|
|
574
|
+
"otilde",
|
|
575
|
+
"ouml",
|
|
576
|
+
"divide",
|
|
577
|
+
"oslash",
|
|
578
|
+
"ugrave",
|
|
579
|
+
"uacute",
|
|
580
|
+
"ucirc",
|
|
581
|
+
"uuml",
|
|
582
|
+
"yacute",
|
|
583
|
+
"thorn",
|
|
584
|
+
"yuml"
|
|
585
|
+
]);
|
|
435
586
|
var ContentValidator = class {
|
|
436
587
|
validate(context, registry, refValidator) {
|
|
437
588
|
const packageDoc = context.packageDocument;
|
|
@@ -457,7 +608,31 @@ var ContentValidator = class {
|
|
|
457
608
|
}
|
|
458
609
|
const cssContent = new TextDecoder().decode(cssData);
|
|
459
610
|
const cssValidator = new CSSValidator();
|
|
460
|
-
cssValidator.validate(context, cssContent, path);
|
|
611
|
+
const result = cssValidator.validate(context, cssContent, path);
|
|
612
|
+
const cssDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
613
|
+
for (const ref of result.references) {
|
|
614
|
+
if (ref.type === "font") {
|
|
615
|
+
const resolvedPath = this.resolveRelativePath(cssDir, ref.url, opfDir);
|
|
616
|
+
const hashIndex = resolvedPath.indexOf("#");
|
|
617
|
+
const targetResource = hashIndex >= 0 ? resolvedPath.slice(0, hashIndex) : resolvedPath;
|
|
618
|
+
refValidator.addReference({
|
|
619
|
+
url: ref.url,
|
|
620
|
+
targetResource,
|
|
621
|
+
type: "font" /* FONT */,
|
|
622
|
+
location: { path }
|
|
623
|
+
});
|
|
624
|
+
} else if (ref.type === "image") {
|
|
625
|
+
const resolvedPath = this.resolveRelativePath(cssDir, ref.url, opfDir);
|
|
626
|
+
const hashIndex = resolvedPath.indexOf("#");
|
|
627
|
+
const targetResource = hashIndex >= 0 ? resolvedPath.slice(0, hashIndex) : resolvedPath;
|
|
628
|
+
refValidator.addReference({
|
|
629
|
+
url: ref.url,
|
|
630
|
+
targetResource,
|
|
631
|
+
type: "image" /* IMAGE */,
|
|
632
|
+
location: { path }
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
461
636
|
this.extractCSSImports(path, cssContent, opfDir, refValidator);
|
|
462
637
|
}
|
|
463
638
|
validateXHTMLDocument(context, path, itemId, opfDir, registry, refValidator) {
|
|
@@ -477,19 +652,26 @@ var ContentValidator = class {
|
|
|
477
652
|
} catch (error) {
|
|
478
653
|
if (error instanceof Error) {
|
|
479
654
|
const { message, line, column } = this.parseLibxmlError(error.message);
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
655
|
+
const entityPattern = /Entity '(\w+)' not defined/;
|
|
656
|
+
const entityExec = entityPattern.exec(error.message);
|
|
657
|
+
const entityName = entityExec?.[1];
|
|
658
|
+
const isKnownHtmlEntity = entityName !== void 0 && HTML_ENTITIES.has(entityName);
|
|
659
|
+
const isEpub2 = context.version === "2.0";
|
|
660
|
+
if (!isEpub2 || !isKnownHtmlEntity) {
|
|
661
|
+
const location = { path };
|
|
662
|
+
if (line !== void 0) {
|
|
663
|
+
location.line = line;
|
|
664
|
+
}
|
|
665
|
+
if (column !== void 0) {
|
|
666
|
+
location.column = column;
|
|
667
|
+
}
|
|
668
|
+
context.messages.push({
|
|
669
|
+
id: "HTM-004",
|
|
670
|
+
severity: "error",
|
|
671
|
+
message,
|
|
672
|
+
location
|
|
673
|
+
});
|
|
486
674
|
}
|
|
487
|
-
context.messages.push({
|
|
488
|
-
id: "HTM-004",
|
|
489
|
-
severity: "error",
|
|
490
|
-
message,
|
|
491
|
-
location
|
|
492
|
-
});
|
|
493
675
|
}
|
|
494
676
|
return;
|
|
495
677
|
}
|
|
@@ -2556,6 +2738,7 @@ var OPFValidator = class {
|
|
|
2556
2738
|
context.packageDocument = this.packageDoc;
|
|
2557
2739
|
this.validatePackageAttributes(context, opfPath);
|
|
2558
2740
|
this.validateMetadata(context, opfPath);
|
|
2741
|
+
this.validateLinkElements(context, opfPath);
|
|
2559
2742
|
this.validateManifest(context, opfPath);
|
|
2560
2743
|
this.validateSpine(context, opfPath);
|
|
2561
2744
|
this.validateFallbackChains(context, opfPath);
|
|
@@ -2807,6 +2990,34 @@ var OPFValidator = class {
|
|
|
2807
2990
|
}
|
|
2808
2991
|
}
|
|
2809
2992
|
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Validate EPUB 3 link elements in metadata
|
|
2995
|
+
*/
|
|
2996
|
+
validateLinkElements(context, opfPath) {
|
|
2997
|
+
if (!this.packageDoc) return;
|
|
2998
|
+
const opfDir = opfPath.includes("/") ? opfPath.substring(0, opfPath.lastIndexOf("/")) : "";
|
|
2999
|
+
for (const link of this.packageDoc.linkElements) {
|
|
3000
|
+
const href = link.href;
|
|
3001
|
+
const decodedHref = tryDecodeUriComponent(href);
|
|
3002
|
+
const basePath = href.includes("#") ? href.substring(0, href.indexOf("#")) : href;
|
|
3003
|
+
const basePathDecoded = decodedHref.includes("#") ? decodedHref.substring(0, decodedHref.indexOf("#")) : decodedHref;
|
|
3004
|
+
if (href.startsWith("#")) {
|
|
3005
|
+
continue;
|
|
3006
|
+
}
|
|
3007
|
+
const resolvedPath = resolvePath(opfDir, basePath);
|
|
3008
|
+
const resolvedPathDecoded = basePathDecoded !== basePath ? resolvePath(opfDir, basePathDecoded) : resolvedPath;
|
|
3009
|
+
const fileExists = context.files.has(resolvedPath) || context.files.has(resolvedPathDecoded);
|
|
3010
|
+
const inManifest = this.manifestByHref.has(basePath) || this.manifestByHref.has(basePathDecoded);
|
|
3011
|
+
if (!fileExists && !inManifest) {
|
|
3012
|
+
context.messages.push({
|
|
3013
|
+
id: "RSC-007",
|
|
3014
|
+
severity: "warning",
|
|
3015
|
+
message: `Referenced resource "${resolvedPath}" could not be found in the EPUB`,
|
|
3016
|
+
location: { path: opfPath }
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
2810
3021
|
/**
|
|
2811
3022
|
* Validate manifest section
|
|
2812
3023
|
*/
|
|
@@ -2834,7 +3045,28 @@ var OPFValidator = class {
|
|
|
2834
3045
|
}
|
|
2835
3046
|
seenHrefs.add(item.href);
|
|
2836
3047
|
const fullPath = resolvePath(opfPath, item.href);
|
|
2837
|
-
if (
|
|
3048
|
+
if (fullPath === opfPath) {
|
|
3049
|
+
context.messages.push({
|
|
3050
|
+
id: "OPF-099",
|
|
3051
|
+
severity: "error",
|
|
3052
|
+
message: "The manifest must not list the package document",
|
|
3053
|
+
location: { path: opfPath }
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
if (!item.href.startsWith("http") && !item.href.startsWith("mailto:")) {
|
|
3057
|
+
const leaked = checkUrlLeaking(item.href);
|
|
3058
|
+
if (leaked) {
|
|
3059
|
+
context.messages.push({
|
|
3060
|
+
id: "RSC-026",
|
|
3061
|
+
severity: "error",
|
|
3062
|
+
message: `URL "${item.href}" leaks outside the container (it is not a valid-relative-ocf-URL-with-fragment string)`,
|
|
3063
|
+
location: { path: opfPath }
|
|
3064
|
+
});
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
const decodedHref = tryDecodeUriComponent(item.href);
|
|
3068
|
+
const fullPathDecoded = decodedHref !== item.href ? resolvePath(opfPath, decodedHref) : fullPath;
|
|
3069
|
+
if (!context.files.has(fullPath) && !context.files.has(fullPathDecoded) && !item.href.startsWith("http")) {
|
|
2838
3070
|
context.messages.push({
|
|
2839
3071
|
id: "RSC-001",
|
|
2840
3072
|
severity: "error",
|
|
@@ -3186,6 +3418,24 @@ function resolvePath(basePath, relativePath) {
|
|
|
3186
3418
|
}
|
|
3187
3419
|
return parts.join("/");
|
|
3188
3420
|
}
|
|
3421
|
+
function tryDecodeUriComponent(encoded) {
|
|
3422
|
+
try {
|
|
3423
|
+
return decodeURIComponent(encoded);
|
|
3424
|
+
} catch {
|
|
3425
|
+
return encoded;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
function checkUrlLeaking(href) {
|
|
3429
|
+
const TEST_BASE_A = "https://a.example.org/A/";
|
|
3430
|
+
const TEST_BASE_B = "https://b.example.org/B/";
|
|
3431
|
+
try {
|
|
3432
|
+
const urlA = new URL(href, TEST_BASE_A).toString();
|
|
3433
|
+
const urlB = new URL(href, TEST_BASE_B).toString();
|
|
3434
|
+
return !urlA.startsWith(TEST_BASE_A) || !urlB.startsWith(TEST_BASE_B);
|
|
3435
|
+
} catch {
|
|
3436
|
+
return false;
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3189
3439
|
function isValidMimeType(mediaType) {
|
|
3190
3440
|
const mimeTypePattern = /^[a-zA-Z][a-zA-Z0-9!#$&\-^_.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-^_.+]*(?:\s*;\s*[a-zA-Z0-9-]+=[^;]+)?$/;
|
|
3191
3441
|
if (!mimeTypePattern.test(mediaType)) {
|
|
@@ -4166,6 +4416,7 @@ var MessageId = /* @__PURE__ */ ((MessageId2) => {
|
|
|
4166
4416
|
MessageId2["OPF_013"] = "OPF-013";
|
|
4167
4417
|
MessageId2["OPF_014"] = "OPF-014";
|
|
4168
4418
|
MessageId2["OPF_097"] = "OPF-097";
|
|
4419
|
+
MessageId2["OPF_099"] = "OPF-099";
|
|
4169
4420
|
MessageId2["OPF_015"] = "OPF-015";
|
|
4170
4421
|
MessageId2["RSC_001"] = "RSC-001";
|
|
4171
4422
|
MessageId2["RSC_002"] = "RSC-002";
|