@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.
Files changed (66) hide show
  1. package/README.md +8 -8
  2. package/bin/epubcheck.js +4 -4
  3. package/bin/epubcheck.ts +4 -4
  4. package/dist/index.cjs +265 -14
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +1 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +265 -14
  9. package/dist/index.js.map +1 -1
  10. package/package.json +2 -2
  11. package/schemas/applications.rng +0 -429
  12. package/schemas/aria.rng +0 -3355
  13. package/schemas/block.rng +0 -488
  14. package/schemas/common.rng +0 -1076
  15. package/schemas/container.rng +0 -24
  16. package/schemas/core-scripting.rng +0 -950
  17. package/schemas/data.rng +0 -161
  18. package/schemas/datatypes.rng +0 -401
  19. package/schemas/embed.rng +0 -980
  20. package/schemas/epub-mathml3-inc.rng +0 -161
  21. package/schemas/epub-nav-30.rnc +0 -44
  22. package/schemas/epub-nav-30.rng +0 -19985
  23. package/schemas/epub-nav-30.sch +0 -87
  24. package/schemas/epub-prefix-attr.rng +0 -17
  25. package/schemas/epub-shared-inc.rng +0 -29
  26. package/schemas/epub-ssml-attrs.rng +0 -17
  27. package/schemas/epub-svg-30.rnc +0 -17
  28. package/schemas/epub-svg-30.rng +0 -19903
  29. package/schemas/epub-svg-30.sch +0 -7
  30. package/schemas/epub-svg-forgiving-inc.rng +0 -315
  31. package/schemas/epub-switch.rng +0 -121
  32. package/schemas/epub-trigger.rng +0 -90
  33. package/schemas/epub-type-attr.rng +0 -12
  34. package/schemas/epub-xhtml-30.rnc +0 -6
  35. package/schemas/epub-xhtml-30.rng +0 -19882
  36. package/schemas/epub-xhtml-30.sch +0 -409
  37. package/schemas/epub-xhtml-inc.rng +0 -151
  38. package/schemas/epub-xhtml-integration.rng +0 -565
  39. package/schemas/epub-xhtml-svg-mathml.rng +0 -17
  40. package/schemas/form-datatypes.rng +0 -54
  41. package/schemas/mathml3-common.rng +0 -336
  42. package/schemas/mathml3-content.rng +0 -1552
  43. package/schemas/mathml3-inc.rng +0 -30
  44. package/schemas/mathml3-presentation.rng +0 -2341
  45. package/schemas/mathml3-strict-content.rng +0 -205
  46. package/schemas/media.rng +0 -374
  47. package/schemas/meta.rng +0 -754
  48. package/schemas/microdata.rng +0 -192
  49. package/schemas/ncx.rng +0 -308
  50. package/schemas/ocf-container-30.rnc +0 -37
  51. package/schemas/ocf-container-30.rng +0 -568
  52. package/schemas/opf.rng +0 -15
  53. package/schemas/opf20.rng +0 -513
  54. package/schemas/package-30.rnc +0 -133
  55. package/schemas/package-30.rng +0 -1153
  56. package/schemas/package-30.sch +0 -444
  57. package/schemas/phrase.rng +0 -746
  58. package/schemas/rdfa.rng +0 -552
  59. package/schemas/revision.rng +0 -106
  60. package/schemas/ruby.rng +0 -141
  61. package/schemas/sectional.rng +0 -278
  62. package/schemas/structural.rng +0 -298
  63. package/schemas/tables.rng +0 -420
  64. package/schemas/web-components.rng +0 -184
  65. package/schemas/web-forms.rng +0 -975
  66. 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 location = { path };
481
- if (line !== void 0) {
482
- location.line = line;
483
- }
484
- if (column !== void 0) {
485
- location.column = column;
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 (!context.files.has(fullPath) && !item.href.startsWith("http")) {
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";