@likecoin/epubcheck-ts 0.3.1 → 0.3.2
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 +1 -1
- package/bin/epubcheck.js +1 -1
- package/bin/epubcheck.ts +1 -1
- package/dist/index.cjs +161 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +161 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A TypeScript port of [EPUBCheck](https://github.com/w3c/epubcheck) - the officia
|
|
|
6
6
|
[](https://www.npmjs.com/package/@likecoin/epubcheck-ts)
|
|
7
7
|
[](./LICENSE)
|
|
8
8
|
|
|
9
|
-
> **Note**: This library is primarily developed for internal use at [3ook.com](https://3ook.com/about) and is built with AI-assisted development. While it has comprehensive test coverage (
|
|
9
|
+
> **Note**: This library is primarily developed for internal use at [3ook.com](https://3ook.com/about) and is built with AI-assisted development. While it has comprehensive test coverage (476 tests) and ~70% feature parity with Java EPUBCheck, it may not be suitable for mission-critical production workloads. For production environments requiring full EPUB validation, consider using the official [Java EPUBCheck](https://github.com/w3c/epubcheck). Contributions and feedback are welcome!
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
package/bin/epubcheck.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { basename } from "node:path";
|
|
5
5
|
const { EpubCheck, toJSONReport } = await import("../dist/index.js");
|
|
6
|
-
const VERSION = "0.3.
|
|
6
|
+
const VERSION = "0.3.2";
|
|
7
7
|
const { values, positionals } = parseArgs({
|
|
8
8
|
options: {
|
|
9
9
|
json: { type: "string", short: "j" },
|
package/bin/epubcheck.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { basename } from 'node:path';
|
|
|
14
14
|
// Dynamic import to support both ESM and CJS builds
|
|
15
15
|
const { EpubCheck, toJSONReport } = await import('../dist/index.js');
|
|
16
16
|
|
|
17
|
-
const VERSION = '0.3.
|
|
17
|
+
const VERSION = '0.3.2';
|
|
18
18
|
|
|
19
19
|
// Parse command line arguments
|
|
20
20
|
const { values, positionals } = parseArgs({
|
package/dist/index.cjs
CHANGED
|
@@ -1758,9 +1758,27 @@ var ContentValidator = class {
|
|
|
1758
1758
|
} else if (item.mediaType === "text/css" && refValidator) {
|
|
1759
1759
|
const fullPath = opfDir ? `${opfDir}/${item.href}` : item.href;
|
|
1760
1760
|
this.validateCSSDocument(context, fullPath, opfDir, refValidator);
|
|
1761
|
+
} else if (item.mediaType === "image/svg+xml" && registry) {
|
|
1762
|
+
const fullPath = opfDir ? `${opfDir}/${item.href}` : item.href;
|
|
1763
|
+
this.extractSVGIDs(context, fullPath, registry);
|
|
1761
1764
|
}
|
|
1762
1765
|
}
|
|
1763
1766
|
}
|
|
1767
|
+
extractSVGIDs(context, path, registry) {
|
|
1768
|
+
const svgData = context.files.get(path);
|
|
1769
|
+
if (!svgData) {
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
const svgContent = new TextDecoder().decode(svgData);
|
|
1773
|
+
let doc;
|
|
1774
|
+
try {
|
|
1775
|
+
doc = libxml2Wasm.XmlDocument.fromString(svgContent);
|
|
1776
|
+
this.extractAndRegisterIDs(path, doc.root, registry);
|
|
1777
|
+
} catch {
|
|
1778
|
+
} finally {
|
|
1779
|
+
doc?.dispose();
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1764
1782
|
validateCSSDocument(context, path, opfDir, refValidator) {
|
|
1765
1783
|
const cssData = context.files.get(path);
|
|
1766
1784
|
if (!cssData) {
|
|
@@ -1958,9 +1976,11 @@ var ContentValidator = class {
|
|
|
1958
1976
|
this.extractAndRegisterIDs(path, root, registry);
|
|
1959
1977
|
}
|
|
1960
1978
|
if (refValidator && opfDir !== void 0) {
|
|
1961
|
-
this.extractAndRegisterHyperlinks(path, root, opfDir, refValidator);
|
|
1979
|
+
this.extractAndRegisterHyperlinks(context, path, root, opfDir, refValidator);
|
|
1962
1980
|
this.extractAndRegisterStylesheets(path, root, opfDir, refValidator);
|
|
1963
1981
|
this.extractAndRegisterImages(path, root, opfDir, refValidator);
|
|
1982
|
+
this.extractAndRegisterMathMLAltimg(path, root, opfDir, refValidator);
|
|
1983
|
+
this.extractAndRegisterScripts(path, root, opfDir, refValidator);
|
|
1964
1984
|
this.extractAndRegisterCiteAttributes(path, root, opfDir, refValidator);
|
|
1965
1985
|
this.extractAndRegisterMediaElements(path, root, opfDir, refValidator);
|
|
1966
1986
|
}
|
|
@@ -2425,12 +2445,20 @@ var ContentValidator = class {
|
|
|
2425
2445
|
}
|
|
2426
2446
|
}
|
|
2427
2447
|
}
|
|
2428
|
-
extractAndRegisterHyperlinks(path, root, opfDir, refValidator) {
|
|
2448
|
+
extractAndRegisterHyperlinks(context, path, root, opfDir, refValidator) {
|
|
2429
2449
|
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
2430
2450
|
const links = root.find(".//html:a[@href]", { html: "http://www.w3.org/1999/xhtml" });
|
|
2431
2451
|
for (const link of links) {
|
|
2432
2452
|
const href = this.getAttribute(link, "href");
|
|
2433
|
-
if (
|
|
2453
|
+
if (href === null) continue;
|
|
2454
|
+
if (href.trim() === "") {
|
|
2455
|
+
pushMessage(context.messages, {
|
|
2456
|
+
id: MessageId.HTM_045,
|
|
2457
|
+
message: "Encountered empty href",
|
|
2458
|
+
location: { path, line: link.line }
|
|
2459
|
+
});
|
|
2460
|
+
continue;
|
|
2461
|
+
}
|
|
2434
2462
|
const line = link.line;
|
|
2435
2463
|
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
2436
2464
|
continue;
|
|
@@ -2570,7 +2598,8 @@ var ContentValidator = class {
|
|
|
2570
2598
|
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
2571
2599
|
const images = root.find(".//html:img[@src]", { html: "http://www.w3.org/1999/xhtml" });
|
|
2572
2600
|
for (const img of images) {
|
|
2573
|
-
const
|
|
2601
|
+
const imgElem = img;
|
|
2602
|
+
const src = this.getAttribute(imgElem, "src");
|
|
2574
2603
|
if (!src) continue;
|
|
2575
2604
|
const line = img.line;
|
|
2576
2605
|
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
@@ -2580,15 +2609,26 @@ var ContentValidator = class {
|
|
|
2580
2609
|
type: "image" /* IMAGE */,
|
|
2581
2610
|
location: { path, line }
|
|
2582
2611
|
});
|
|
2583
|
-
|
|
2612
|
+
} else {
|
|
2613
|
+
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
2614
|
+
const hashIndex = resolvedPath.indexOf("#");
|
|
2615
|
+
const targetResource = hashIndex >= 0 ? resolvedPath.slice(0, hashIndex) : resolvedPath;
|
|
2616
|
+
const fragment = hashIndex >= 0 ? resolvedPath.slice(hashIndex + 1) : void 0;
|
|
2617
|
+
const ref = {
|
|
2618
|
+
url: src,
|
|
2619
|
+
targetResource,
|
|
2620
|
+
type: "image" /* IMAGE */,
|
|
2621
|
+
location: { path, line }
|
|
2622
|
+
};
|
|
2623
|
+
if (fragment) {
|
|
2624
|
+
ref.fragment = fragment;
|
|
2625
|
+
}
|
|
2626
|
+
refValidator.addReference(ref);
|
|
2627
|
+
}
|
|
2628
|
+
const srcset = this.getAttribute(imgElem, "srcset");
|
|
2629
|
+
if (srcset) {
|
|
2630
|
+
this.parseSrcset(srcset, docDir, opfDir, path, line, refValidator);
|
|
2584
2631
|
}
|
|
2585
|
-
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
2586
|
-
refValidator.addReference({
|
|
2587
|
-
url: src,
|
|
2588
|
-
targetResource: resolvedPath,
|
|
2589
|
-
type: "image" /* IMAGE */,
|
|
2590
|
-
location: { path, line }
|
|
2591
|
-
});
|
|
2592
2632
|
}
|
|
2593
2633
|
let svgImages = [];
|
|
2594
2634
|
try {
|
|
@@ -2618,12 +2658,19 @@ var ContentValidator = class {
|
|
|
2618
2658
|
continue;
|
|
2619
2659
|
}
|
|
2620
2660
|
const resolvedPath = this.resolveRelativePath(docDir, href, opfDir);
|
|
2621
|
-
|
|
2661
|
+
const hashIndex = resolvedPath.indexOf("#");
|
|
2662
|
+
const targetResource = hashIndex >= 0 ? resolvedPath.slice(0, hashIndex) : resolvedPath;
|
|
2663
|
+
const fragment = hashIndex >= 0 ? resolvedPath.slice(hashIndex + 1) : void 0;
|
|
2664
|
+
const svgImgRef = {
|
|
2622
2665
|
url: href,
|
|
2623
|
-
targetResource
|
|
2666
|
+
targetResource,
|
|
2624
2667
|
type: "image" /* IMAGE */,
|
|
2625
2668
|
location: { path, line }
|
|
2626
|
-
}
|
|
2669
|
+
};
|
|
2670
|
+
if (fragment) {
|
|
2671
|
+
svgImgRef.fragment = fragment;
|
|
2672
|
+
}
|
|
2673
|
+
refValidator.addReference(svgImgRef);
|
|
2627
2674
|
}
|
|
2628
2675
|
const videos = root.find(".//html:video[@poster]", { html: "http://www.w3.org/1999/xhtml" });
|
|
2629
2676
|
for (const video of videos) {
|
|
@@ -2648,6 +2695,58 @@ var ContentValidator = class {
|
|
|
2648
2695
|
});
|
|
2649
2696
|
}
|
|
2650
2697
|
}
|
|
2698
|
+
extractAndRegisterMathMLAltimg(path, root, opfDir, refValidator) {
|
|
2699
|
+
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
2700
|
+
const mathElements = root.find(".//math:math[@altimg]", {
|
|
2701
|
+
math: "http://www.w3.org/1998/Math/MathML"
|
|
2702
|
+
});
|
|
2703
|
+
for (const mathElem of mathElements) {
|
|
2704
|
+
const altimg = this.getAttribute(mathElem, "altimg");
|
|
2705
|
+
if (!altimg) continue;
|
|
2706
|
+
const line = mathElem.line;
|
|
2707
|
+
if (altimg.startsWith("http://") || altimg.startsWith("https://")) {
|
|
2708
|
+
refValidator.addReference({
|
|
2709
|
+
url: altimg,
|
|
2710
|
+
targetResource: altimg,
|
|
2711
|
+
type: "image" /* IMAGE */,
|
|
2712
|
+
location: { path, line }
|
|
2713
|
+
});
|
|
2714
|
+
continue;
|
|
2715
|
+
}
|
|
2716
|
+
const resolvedPath = this.resolveRelativePath(docDir, altimg, opfDir);
|
|
2717
|
+
refValidator.addReference({
|
|
2718
|
+
url: altimg,
|
|
2719
|
+
targetResource: resolvedPath,
|
|
2720
|
+
type: "image" /* IMAGE */,
|
|
2721
|
+
location: { path, line }
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
extractAndRegisterScripts(path, root, opfDir, refValidator) {
|
|
2726
|
+
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
2727
|
+
const scripts = root.find(".//html:script[@src]", { html: "http://www.w3.org/1999/xhtml" });
|
|
2728
|
+
for (const script of scripts) {
|
|
2729
|
+
const src = this.getAttribute(script, "src");
|
|
2730
|
+
if (!src) continue;
|
|
2731
|
+
const line = script.line;
|
|
2732
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
2733
|
+
refValidator.addReference({
|
|
2734
|
+
url: src,
|
|
2735
|
+
targetResource: src,
|
|
2736
|
+
type: "generic" /* GENERIC */,
|
|
2737
|
+
location: { path, line }
|
|
2738
|
+
});
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
2742
|
+
refValidator.addReference({
|
|
2743
|
+
url: src,
|
|
2744
|
+
targetResource: resolvedPath,
|
|
2745
|
+
type: "generic" /* GENERIC */,
|
|
2746
|
+
location: { path, line }
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2651
2750
|
/**
|
|
2652
2751
|
* Extract cite attribute references from blockquote, q, ins, del elements
|
|
2653
2752
|
* These need to be validated as RSC-007 if the referenced resource is missing
|
|
@@ -2798,6 +2897,32 @@ var ContentValidator = class {
|
|
|
2798
2897
|
}
|
|
2799
2898
|
}
|
|
2800
2899
|
}
|
|
2900
|
+
parseSrcset(srcset, docDir, opfDir, path, line, refValidator) {
|
|
2901
|
+
const entries = srcset.split(",");
|
|
2902
|
+
for (const entry of entries) {
|
|
2903
|
+
const trimmed = entry.trim();
|
|
2904
|
+
if (!trimmed) continue;
|
|
2905
|
+
const url = trimmed.split(/\s+/)[0];
|
|
2906
|
+
if (!url) continue;
|
|
2907
|
+
const location = line !== void 0 ? { path, line } : { path };
|
|
2908
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
2909
|
+
refValidator.addReference({
|
|
2910
|
+
url,
|
|
2911
|
+
targetResource: url,
|
|
2912
|
+
type: "image" /* IMAGE */,
|
|
2913
|
+
location
|
|
2914
|
+
});
|
|
2915
|
+
} else {
|
|
2916
|
+
const resolvedPath = this.resolveRelativePath(docDir, url, opfDir);
|
|
2917
|
+
refValidator.addReference({
|
|
2918
|
+
url,
|
|
2919
|
+
targetResource: resolvedPath,
|
|
2920
|
+
type: "image" /* IMAGE */,
|
|
2921
|
+
location
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2801
2926
|
resolveRelativePath(docDir, href, _opfDir) {
|
|
2802
2927
|
const hrefWithoutFragment = href.split("#")[0] ?? href;
|
|
2803
2928
|
const fragment = href.includes("#") ? href.split("#")[1] : "";
|
|
@@ -4838,11 +4963,19 @@ var ReferenceValidator = class {
|
|
|
4838
4963
|
}
|
|
4839
4964
|
if (isDataURL(url)) {
|
|
4840
4965
|
if (this.version.startsWith("3.")) {
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4966
|
+
const forbiddenDataUrlTypes = [
|
|
4967
|
+
"hyperlink" /* HYPERLINK */,
|
|
4968
|
+
"nav-toc-link" /* NAV_TOC_LINK */,
|
|
4969
|
+
"nav-pagelist-link" /* NAV_PAGELIST_LINK */,
|
|
4970
|
+
"cite" /* CITE */
|
|
4971
|
+
];
|
|
4972
|
+
if (forbiddenDataUrlTypes.includes(reference.type)) {
|
|
4973
|
+
pushMessage(context.messages, {
|
|
4974
|
+
id: MessageId.RSC_029,
|
|
4975
|
+
message: "Data URLs are not allowed in this context",
|
|
4976
|
+
location: reference.location
|
|
4977
|
+
});
|
|
4978
|
+
}
|
|
4846
4979
|
}
|
|
4847
4980
|
return;
|
|
4848
4981
|
}
|
|
@@ -4982,6 +5115,14 @@ var ReferenceValidator = class {
|
|
|
4982
5115
|
});
|
|
4983
5116
|
return;
|
|
4984
5117
|
}
|
|
5118
|
+
if (reference.type === "image" /* IMAGE */ && resource?.mimeType !== "image/svg+xml") {
|
|
5119
|
+
pushMessage(context.messages, {
|
|
5120
|
+
id: MessageId.RSC_009,
|
|
5121
|
+
message: `Fragment identifier used on a non-SVG image resource: ${resourcePath}#${fragment}`,
|
|
5122
|
+
location: reference.location
|
|
5123
|
+
});
|
|
5124
|
+
return;
|
|
5125
|
+
}
|
|
4985
5126
|
if (resource?.mimeType === "image/svg+xml") {
|
|
4986
5127
|
const hasSVGView = fragment.includes("svgView(") || fragment.includes("viewBox(");
|
|
4987
5128
|
if (hasSVGView && reference.type === "hyperlink" /* HYPERLINK */) {
|