From f20993e8e80289275ed915b884fd09f1dcb85b02 Mon Sep 17 00:00:00 2001
From: Romain Deltour
Date: Thu, 8 Dec 2022 15:43:22 +0100
Subject: [PATCH] feat: check that non-linear content is reachable
EPUB 3.3 says:
> EPUB creators MUST provide a means of accessing all non-linear content
> (e.g., hyperlinks in the content or from the EPUB navigation document).
This commit adds a check for the above statement.
- new error `OPF-096` is reported when no hyperlink was found to
non-linear content in an EPUB with no script
- new usage `OPF-096b` is reported when no hyperlink was found to
non-linear content in a scripted EPUB (a link may be added via
scripting, so we cannot report this as an error)
Fix #1451
---
.../epubcheck/messages/DefaultSeverities.java | 2 +
.../adobe/epubcheck/messages/MessageId.java | 2 +
.../com/adobe/epubcheck/opf/OPFChecker30.java | 27 ++++++++-
.../messages/MessageBundle.properties | 4 +-
.../epub-edupub/edupub-publication.feature | 2 +-
.../edupub-non-linear-valid/EPUB/nav.xhtml | 1 +
.../edupub-non-linear-valid/EPUB/package.opf | 4 +-
.../data-nav-in-spine-warning/EPUB/nav.xhtml | 1 +
.../files/spine-empty-error.opf | 16 ++++++
.../package.opf => spine-missing-error.opf} | 0
.../files/spine-no-linear-itemref-error.opf | 16 ++++++
.../EPUB/content_001.xhtml | 11 ++++
.../EPUB/content_002.xhtml | 11 ++++
.../EPUB/nav.xhtml | 0
.../EPUB/package.opf | 18 ++++++
.../META-INF/container.xml | 0
.../mimetype | 0
.../EPUB/content_001.xhtml | 12 ++++
.../EPUB/content_002.xhtml | 11 ++++
.../EPUB/nav.xhtml | 0
.../EPUB/package.opf | 18 ++++++
.../META-INF/container.xml | 6 ++
.../mimetype | 0
.../EPUB/content_001.xhtml | 11 ++++
.../EPUB/content_002.xhtml | 11 ++++
.../EPUB/nav.xhtml | 15 +++++
.../EPUB/package.opf | 18 ++++++
.../META-INF/container.xml | 6 ++
.../mimetype | 1 +
.../EPUB/content_001.xhtml | 19 +++++++
.../EPUB/content_002.xhtml | 11 ++++
.../EPUB/nav.xhtml | 14 +++++
.../EPUB/package.opf | 18 ++++++
.../META-INF/container.xml | 6 ++
.../mimetype | 1 +
.../EPUB/content_001.xhtml | 0
.../EPUB/content_002.xhtml | 0
.../EPUB/nav.xhtml | 14 +++++
.../EPUB/package.opf | 0
.../META-INF/container.xml | 0
.../mimetype | 1 +
.../EPUB/content_001.xhtml | 0
.../EPUB/content_002.xhtml | 11 ++++
.../EPUB/nav.xhtml | 15 +++++
.../EPUB/package.opf | 17 ++++++
.../META-INF/container.xml | 6 ++
.../mimetype | 1 +
.../package-document.feature | 55 +++++++++++++++++--
.../content-document-xhtml.feature | 6 --
.../EPUB/package.opf | 1 -
.../EPUB/package.opf | 1 -
.../EPUB/package.opf | 1 -
.../EPUB/package.opf | 1 -
.../content-fxl-svg-valid/EPUB/package.opf | 1 -
54 files changed, 402 insertions(+), 22 deletions(-)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-empty-error.opf
rename src/test/resources/epub3/05-package-document/files/{package-spine-missing-error/EPUB/package.opf => spine-missing-error.opf} (100%)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-no-linear-itemref-error.opf
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-not-reachable-error/EPUB/content_001.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-not-reachable-error/EPUB/content_002.xhtml
rename src/test/resources/epub3/05-package-document/files/{package-spine-missing-error => spine-nonlinear-not-reachable-error}/EPUB/nav.xhtml (100%)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-not-reachable-error/EPUB/package.opf
rename src/test/resources/epub3/05-package-document/files/{package-spine-missing-error => spine-nonlinear-not-reachable-error}/META-INF/container.xml (100%)
rename src/test/resources/epub3/05-package-document/files/{package-spine-missing-error => spine-nonlinear-not-reachable-error}/mimetype (100%)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-hyperlink-valid/EPUB/content_001.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-hyperlink-valid/EPUB/content_002.xhtml
rename src/test/resources/epub3/{06-content-document/files/content-xhtml-link-out-of-spine-error => 05-package-document/files/spine-nonlinear-reachable-via-hyperlink-valid}/EPUB/nav.xhtml (100%)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-hyperlink-valid/EPUB/package.opf
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-hyperlink-valid/META-INF/container.xml
rename src/test/resources/epub3/{06-content-document/files/content-xhtml-link-out-of-spine-error => 05-package-document/files/spine-nonlinear-reachable-via-hyperlink-valid}/mimetype (100%)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-nav-valid/EPUB/content_001.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-nav-valid/EPUB/content_002.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-nav-valid/EPUB/nav.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-nav-valid/EPUB/package.opf
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-nav-valid/META-INF/container.xml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-nav-valid/mimetype
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-script-valid/EPUB/content_001.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-script-valid/EPUB/content_002.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-script-valid/EPUB/nav.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-script-valid/EPUB/package.opf
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-script-valid/META-INF/container.xml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-nonlinear-reachable-via-script-valid/mimetype
rename src/test/resources/epub3/{06-content-document/files/content-xhtml-link-out-of-spine-error => 05-package-document/files/spine-not-listing-hyperlink-target-error}/EPUB/content_001.xhtml (100%)
rename src/test/resources/epub3/{06-content-document/files/content-xhtml-link-out-of-spine-error => 05-package-document/files/spine-not-listing-hyperlink-target-error}/EPUB/content_002.xhtml (100%)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-not-listing-hyperlink-target-error/EPUB/nav.xhtml
rename src/test/resources/epub3/{06-content-document/files/content-xhtml-link-out-of-spine-error => 05-package-document/files/spine-not-listing-hyperlink-target-error}/EPUB/package.opf (100%)
rename src/test/resources/epub3/{06-content-document/files/content-xhtml-link-out-of-spine-error => 05-package-document/files/spine-not-listing-hyperlink-target-error}/META-INF/container.xml (100%)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-not-listing-hyperlink-target-error/mimetype
rename src/test/resources/epub3/05-package-document/files/{package-spine-missing-error => spine-not-listing-navigation-document-target-error}/EPUB/content_001.xhtml (100%)
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-not-listing-navigation-document-target-error/EPUB/content_002.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-not-listing-navigation-document-target-error/EPUB/nav.xhtml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-not-listing-navigation-document-target-error/EPUB/package.opf
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-not-listing-navigation-document-target-error/META-INF/container.xml
create mode 100644 src/test/resources/epub3/05-package-document/files/spine-not-listing-navigation-document-target-error/mimetype
diff --git a/src/main/java/com/adobe/epubcheck/messages/DefaultSeverities.java b/src/main/java/com/adobe/epubcheck/messages/DefaultSeverities.java
index 792578953..a007f659c 100644
--- a/src/main/java/com/adobe/epubcheck/messages/DefaultSeverities.java
+++ b/src/main/java/com/adobe/epubcheck/messages/DefaultSeverities.java
@@ -289,6 +289,8 @@ private void initialize()
severities.put(MessageId.OPF_093, Severity.ERROR);
severities.put(MessageId.OPF_094, Severity.ERROR);
severities.put(MessageId.OPF_095, Severity.ERROR);
+ severities.put(MessageId.OPF_096, Severity.ERROR);
+ severities.put(MessageId.OPF_096b, Severity.USAGE);
// PKG
severities.put(MessageId.PKG_001, Severity.WARNING);
diff --git a/src/main/java/com/adobe/epubcheck/messages/MessageId.java b/src/main/java/com/adobe/epubcheck/messages/MessageId.java
index b74f13168..c0a835807 100644
--- a/src/main/java/com/adobe/epubcheck/messages/MessageId.java
+++ b/src/main/java/com/adobe/epubcheck/messages/MessageId.java
@@ -283,6 +283,8 @@ public enum MessageId implements Comparable
OPF_093("OPF-093"),
OPF_094("OPF-094"),
OPF_095("OPF-095"),
+ OPF_096("OPF-096"),
+ OPF_096b("OPF-096b"),
// Messages relating to the entire package
PKG_001("PKG-001"),
diff --git a/src/main/java/com/adobe/epubcheck/opf/OPFChecker30.java b/src/main/java/com/adobe/epubcheck/opf/OPFChecker30.java
index 38427d3cb..011993962 100644
--- a/src/main/java/com/adobe/epubcheck/opf/OPFChecker30.java
+++ b/src/main/java/com/adobe/epubcheck/opf/OPFChecker30.java
@@ -24,6 +24,7 @@
import java.util.Set;
+import org.w3c.epubcheck.core.references.Reference;
import org.w3c.epubcheck.util.url.URLFragment;
import com.adobe.epubcheck.api.EPUBLocation;
@@ -183,6 +184,26 @@ else if (!overlayTextChecker.isCorrectOverlay(docURL, mo))
}
}
}
+
+ // check that non-linear content documents are reachable
+ if (item.isInSpine() && !item.isLinear() && context.referenceRegistry.isPresent()
+ // search the reference registry for any hyperlink pointing to this item
+ && !context.referenceRegistry.get().asList().stream()
+ .anyMatch(ref -> ref.type == Reference.Type.HYPERLINK
+ && ref.targetResource.equals(item.getURL())))
+ {
+ // if content is scripted, references can be added by scripting
+ // se we only report a usage
+ if (context.featureReport.hasFeature(FeatureEnum.HAS_SCRIPTS))
+ {
+ report.message(MessageId.OPF_096b, item.getLocation(), item.getPath());
+ }
+ // else, report an error if no hyperlink were found
+ else
+ {
+ report.message(MessageId.OPF_096, item.getLocation(), item.getPath());
+ }
+ }
}
@Override
@@ -194,14 +215,15 @@ protected void checkSpineItem(OPFItem item, OPFHandler opfHandler)
return;
}
- String mimeType = item.getMimeType();
-
+ // check properties
if (item.getProperties()
.contains(PackageVocabs.ITEM_VOCAB.get(PackageVocabs.ITEM_PROPERTIES.DATA_NAV)))
{
report.message(MessageId.OPF_077, item.getLocation());
}
+ // check that spine items have content document fallback
+ String mimeType = item.getMimeType();
if (!isBlessedItemType(mimeType, version))
{
if (!item.hasFallback())
@@ -213,7 +235,6 @@ else if (!item.hasContentDocumentFallback())
report.message(MessageId.OPF_044, item.getLocation(), mimeType);
}
}
-
}
private void checkCollections()
diff --git a/src/main/resources/com/adobe/epubcheck/messages/MessageBundle.properties b/src/main/resources/com/adobe/epubcheck/messages/MessageBundle.properties
index 27fe95bd9..5d9192522 100644
--- a/src/main/resources/com/adobe/epubcheck/messages/MessageBundle.properties
+++ b/src/main/resources/com/adobe/epubcheck/messages/MessageBundle.properties
@@ -210,7 +210,9 @@ OPF_091=The item href URL must not have a fragment identifier.
OPF_092=Language tag "%1$s" is not well-formed: %2$s
OPF_093=The "media-type" attribute is required for linked resources located in the EPUB container
OPF_094=The "media-type" attribute is required for "%1$s" links.
-OPF_095=The "media-type" attribute of "voicing" links must be an audio MIME type, but found "%1$s".
+OPF_095=The "media-type" attribute of "voicing" links must be an audio MIME type, but found "%1$s".
+OPF_096=Non-linear content must be reachable, but found no hyperlink to "%1$s".
+OPF_096b=No hyperlink was found to non-linear document "%1$s", please check that it can be reached from scripted content.
#Package
PKG_001=Validating the EPUB against version %1$s but detected version %2$s.
diff --git a/src/test/resources/epub-edupub/edupub-publication.feature b/src/test/resources/epub-edupub/edupub-publication.feature
index b4e70d7a4..cb1d341c8 100644
--- a/src/test/resources/epub-edupub/edupub-publication.feature
+++ b/src/test/resources/epub-edupub/edupub-publication.feature
@@ -51,7 +51,7 @@ Feature: EPUB for Education ▸ Full Publication Checks
## 4.2 Sectioning
- Scenario: Verify an non-linear content does not have to follow the sectioning rules
+ Scenario: Verify that non-linear content does not have to follow the sectioning rules
When checking EPUB 'edupub-non-linear-valid'
Then no errors or warnings are reported
diff --git a/src/test/resources/epub-edupub/files/epub/edupub-non-linear-valid/EPUB/nav.xhtml b/src/test/resources/epub-edupub/files/epub/edupub-non-linear-valid/EPUB/nav.xhtml
index 240745e63..755db90fb 100644
--- a/src/test/resources/epub-edupub/files/epub/edupub-non-linear-valid/EPUB/nav.xhtml
+++ b/src/test/resources/epub-edupub/files/epub/edupub-non-linear-valid/EPUB/nav.xhtml
@@ -8,6 +8,7 @@
+ Loomings
+ Call me Ishmael.
+
diff --git a/src/test/resources/epub-edupub/files/epub/edupub-non-linear-valid/EPUB/package.opf b/src/test/resources/epub-edupub/files/epub/edupub-non-linear-valid/EPUB/package.opf
index bb13ce5f2..1029ce887 100644
--- a/src/test/resources/epub-edupub/files/epub/edupub-non-linear-valid/EPUB/package.opf
+++ b/src/test/resources/epub-edupub/files/epub/edupub-non-linear-valid/EPUB/package.opf
@@ -11,10 +11,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/src/test/resources/epub-region-nav/files/epub/data-nav-in-spine-warning/EPUB/nav.xhtml b/src/test/resources/epub-region-nav/files/epub/data-nav-in-spine-warning/EPUB/nav.xhtml
index 240745e63..51e90dbc7 100644
--- a/src/test/resources/epub-region-nav/files/epub/data-nav-in-spine-warning/EPUB/nav.xhtml
+++ b/src/test/resources/epub-region-nav/files/epub/data-nav-in-spine-warning/EPUB/nav.xhtml
@@ -8,6 +8,7 @@
diff --git a/src/test/resources/epub3/05-package-document/files/spine-empty-error.opf b/src/test/resources/epub3/05-package-document/files/spine-empty-error.opf
new file mode 100644
index 000000000..9d41c79c4
--- /dev/null
+++ b/src/test/resources/epub3/05-package-document/files/spine-empty-error.opf
@@ -0,0 +1,16 @@
+
+
+
+ Minimal EPUB 3.0
+ en
+ NOID
+ 2017-06-14T00:00:01Z
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/epub3/05-package-document/files/package-spine-missing-error/EPUB/package.opf b/src/test/resources/epub3/05-package-document/files/spine-missing-error.opf
similarity index 100%
rename from src/test/resources/epub3/05-package-document/files/package-spine-missing-error/EPUB/package.opf
rename to src/test/resources/epub3/05-package-document/files/spine-missing-error.opf
diff --git a/src/test/resources/epub3/05-package-document/files/spine-no-linear-itemref-error.opf b/src/test/resources/epub3/05-package-document/files/spine-no-linear-itemref-error.opf
new file mode 100644
index 000000000..7b7beec7a
--- /dev/null
+++ b/src/test/resources/epub3/05-package-document/files/spine-no-linear-itemref-error.opf
@@ -0,0 +1,16 @@
+
+
+
+ Minimal EPUB 3.0
+ en
+ NOID
+ 2017-06-14T00:00:01Z
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/epub3/05-package-document/files/spine-nonlinear-not-reachable-error/EPUB/content_001.xhtml b/src/test/resources/epub3/05-package-document/files/spine-nonlinear-not-reachable-error/EPUB/content_001.xhtml
new file mode 100644
index 000000000..43a520ea2
--- /dev/null
+++ b/src/test/resources/epub3/05-package-document/files/spine-nonlinear-not-reachable-error/EPUB/content_001.xhtml
@@ -0,0 +1,11 @@
+
+
+