Skip to content

Commit

Permalink
feat: check that toc & page-list nav are in reading order
Browse files Browse the repository at this point in the history
New Message:
- `NAV_011` (`ERROR`) is reported when the links in `toc` and `page-list`
  `nav` (in the Navigation Document) do not follow the reading order
  (i.e. spine order + order of IDs in the Content Documents).

Checking logic for `NAV_011`:
1. collect the nav links as references in `XRefChecker`
2. check the reading order consistency in `XRefChecker.checkReferences`
   (ran after all documents have been parsed and validated)

Internal changes:
- In the `PathUtil` utilities:
  - rename the `removeAnchor` method to `removeFragment`
  - add a new `getFragment` method
- In the `OPFItem` class:
  - for spine items, record their position in the spine
  - add a new public `getSpinePosition()` getter
- In the `XRefChecker` class:
  - add a new `getResource(path)` public method to get an `OPFItem`
    for a given path.
  - add a new `getAnchorPosition(id)` method to the internal `Resource`
    class.
  - some registered references are duplicates of already-registered
    references, with a specific reference type (e.g. a Nav Doc link is
    registered both as a HYPERLINK and as a NAV_TOC_LINK): these
    subtypes are now checked in separate private methods, to keep the
    main `checkReference(Reference)` method pristine.
  - add a new `checkReadingOrder(Queue, int, int)` private method which
    processes a queue of references (along with the last known spine
    position and fragment position) and check they are in reading order
- add new tests for the Navigation Document reading order check
- fix existing tests

Fix #888
  • Loading branch information
rdeltour committed Mar 12, 2019
1 parent dd0805f commit 8ba384f
Show file tree
Hide file tree
Showing 65 changed files with 746 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ private void initialize()
severities.put(MessageId.NAV_008, Severity.USAGE);
severities.put(MessageId.NAV_009, Severity.ERROR);
severities.put(MessageId.NAV_010, Severity.ERROR);
severities.put(MessageId.NAV_011, Severity.ERROR);

// NCX
severities.put(MessageId.NCX_001, Severity.ERROR);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/adobe/epubcheck/messages/MessageId.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ public enum MessageId implements Comparable<MessageId>
NAV_008("NAV-008"),
NAV_009("NAV-009"),
NAV_010("NAV-010"),
NAV_011("NAV-011"),

// Epub2 based table of content messages
NCX_001("NCX-001"),
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/com/adobe/epubcheck/nav/NavHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.adobe.epubcheck.api.EPUBLocation;
import com.adobe.epubcheck.messages.MessageId;
import com.adobe.epubcheck.opf.ValidationContext;
import com.adobe.epubcheck.opf.XRefChecker;
import com.adobe.epubcheck.ops.OPSHandler30;
import com.adobe.epubcheck.util.EpubConstants;
import com.adobe.epubcheck.util.FeatureEnum;
Expand Down Expand Up @@ -55,6 +56,7 @@ public void startElement()
String href = e.getAttribute("href");
if (href != null)
{
String resolvedHref = PathUtil.resolveRelativeReference(base, href);
// Feature reporting
if (currentNavType == NavType.TOC)
{
Expand All @@ -66,12 +68,23 @@ public void startElement()
// Note: links to out-of-spine in-container items are already reported
// (RSC_011), so we only need to report links to remote resources
if (EnumSet.of(NavType.TOC, NavType.LANDMARKS, NavType.PAGE_LIST).contains(currentNavType)
&& PathUtil.isRemote(href))
&& PathUtil.isRemote(resolvedHref))
{
report.message(MessageId.NAV_010,
EPUBLocation.create(path, parser.getLineNumber(), parser.getColumnNumber()),
currentNavType, href);
}
// For 'toc' and 'page-list' nav, register special references to the
// cross-reference checker, to be able to check that they are in reading order
// after all the Content Documents have been parsed
else if (xrefChecker.isPresent()
&& (currentNavType == NavType.TOC || currentNavType == NavType.PAGE_LIST))
{
xrefChecker.get().registerReference(path, parser.getLineNumber(),
parser.getColumnNumber(), resolvedHref,
(currentNavType == NavType.TOC) ? XRefChecker.Type.NAV_TOC_LINK
: XRefChecker.Type.NAV_PAGELIST_LINK);
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/adobe/epubcheck/opf/OPFChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ protected void checkGuide()
for (int i = 0; i < refCount; i++)
{
OPFReference ref = opfHandler.getReference(i);
String itemPath = PathUtil.removeAnchor(ref.getHref());
String itemPath = PathUtil.removeFragment(ref.getHref());
Optional<OPFItem> item = opfHandler.getItemByPath(itemPath);
if (!item.isPresent())
{
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/adobe/epubcheck/opf/OPFHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ public class OPFHandler implements XMLHandler
private boolean opf12PackageFile = false;

private boolean checkedUnsupportedXmlVersion = false;

// counts the position of itemrefs in the spine
private int spineItemCounter = 0;

static
{
Expand Down Expand Up @@ -385,7 +388,7 @@ else if (name.equals("itemref"))
OPFItem.Builder item = itemBuilders.get(idref.trim());
if (item != null)
{
item.inSpine();
item.inSpine(spineItemCounter++);
String linear = e.getAttribute("linear");
if (linear != null && "no".equals(linear.trim()))
{
Expand Down
32 changes: 23 additions & 9 deletions src/main/java/com/adobe/epubcheck/opf/OPFItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ public class OPFItem
private final Set<Property> properties;
private final boolean ncx;
private final boolean inSpine;
private final int spinePosition;
private final boolean nav;
private final boolean scripted;
private final boolean linear;
private final boolean fixedLayout;

private OPFItem(String id, String path, String mimetype, int lineNumber, int columnNumber,
Optional<String> fallback, Optional<String> fallbackStyle, Set<Property> properties,
boolean ncx, boolean inSpine, boolean nav, boolean scripted, boolean linear, boolean fxl)
boolean ncx, int spinePosition, boolean nav, boolean scripted, boolean linear, boolean fxl)
{
this.id = id;
this.path = path;
Expand All @@ -67,7 +68,8 @@ private OPFItem(String id, String path, String mimetype, int lineNumber, int col
this.fallbackStyle = fallbackStyle;
this.properties = properties;
this.ncx = ncx;
this.inSpine = inSpine;
this.inSpine = spinePosition > -1;
this.spinePosition = spinePosition;
this.nav = nav;
this.scripted = scripted;
this.linear = linear;
Expand Down Expand Up @@ -137,8 +139,8 @@ public Optional<String> getFallback()
}

/**
* Returns An {@link Optional} containing the ID of the fallback stylesheet
* for this item, if it has one.
* Returns An {@link Optional} containing the ID of the fallback stylesheet for
* this item, if it has one.
*
* @return An optional containing the ID of the fallback stylesheet for this
* item if it has one, or {@link Optional#absent()} otherwise.
Expand All @@ -159,6 +161,18 @@ public Set<Property> getProperties()
return properties;
}

/**
* Returns the zero-based position of this item in the spine, or {@code -1} if
* this item is not in the spine.
*
* @return the position of this item in the spine, or {@code -1} if this item is
* not in the spine.
*/
public int getSpinePosition()
{
return spinePosition;
}

/**
* Returns <code>true</code> iff this item is an NCX document.
*
Expand Down Expand Up @@ -277,7 +291,7 @@ public static final class Builder
private String fallbackStyle = null;
private boolean ncx = false;
private boolean linear = true;
private boolean inSpine = false;
private int spinePosition = -1;
private boolean fxl = false;
private ImmutableSet.Builder<Property> propertiesBuilder = new ImmutableSet.Builder<Property>();

Expand Down Expand Up @@ -337,9 +351,9 @@ public Builder nonlinear()
return this;
}

public Builder inSpine()
public Builder inSpine(int position)
{
this.inSpine = true;
this.spinePosition = Preconditions.checkNotNull(position);
return this;
}

Expand All @@ -357,7 +371,7 @@ public Builder properties(Set<Property> properties)
*/
public OPFItem build()
{
if (!inSpine || !linear)
if (spinePosition < 0 || !linear)
{
this.propertiesBuilder.add(EpubCheckVocab.VOCAB.get(EpubCheckVocab.PROPERTIES.NON_LINEAR));
}
Expand All @@ -371,7 +385,7 @@ public OPFItem build()
return new OPFItem(id, path, mimeType, lineNumber, columnNumber,
Optional.fromNullable(Strings.emptyToNull(Strings.nullToEmpty(fallback).trim())),
Optional.fromNullable(Strings.emptyToNull(Strings.nullToEmpty(fallbackStyle).trim())),
properties, ncx, inSpine,
properties, ncx, spinePosition,
properties.contains(PackageVocabs.ITEM_VOCAB.get(PackageVocabs.ITEM_PROPERTIES.NAV)),
properties.contains(PackageVocabs.ITEM_VOCAB.get(PackageVocabs.ITEM_PROPERTIES.SCRIPTED)),
linear, fxl);
Expand Down
Loading

0 comments on commit 8ba384f

Please sign in to comment.