Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nav Doc link checks (remote resources + reading order) #999

Merged
merged 2 commits into from
Mar 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ private void initialize()
severities.put(MessageId.NAV_007, Severity.USAGE);
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
2 changes: 2 additions & 0 deletions src/main/java/com/adobe/epubcheck/messages/MessageId.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ public enum MessageId implements Comparable<MessageId>
NAV_007("NAV-007"),
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
133 changes: 104 additions & 29 deletions src/main/java/com/adobe/epubcheck/nav/NavHandler.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
package com.adobe.epubcheck.nav;

import java.util.EnumSet;
import java.util.Set;

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;
import com.adobe.epubcheck.util.PathUtil;
import com.adobe.epubcheck.vocab.StructureVocab.EPUB_TYPES;
import com.adobe.epubcheck.xml.XMLElement;
import com.adobe.epubcheck.xml.XMLParser;
import com.google.common.base.CaseFormat;
import com.google.common.base.Converter;

public class NavHandler extends OPSHandler30
{

private boolean inToc = false;
private NavType currentNavType = NavType.NONE;
private boolean isNavTypes = false;

private static enum NavType
{
NONE,
TOC,
LANDMARKS,
PAGE_LIST,
OTHER;

private Converter<String, String> formatter = CaseFormat.UPPER_UNDERSCORE
.converterTo(CaseFormat.LOWER_HYPHEN);

@Override
public String toString()
{
return formatter.convert(this.name());
}
}

NavHandler(ValidationContext context, XMLParser parser)
{
Expand All @@ -24,10 +51,41 @@ public void startElement()
{
super.startElement();
XMLElement e = parser.getCurrentElement();
String name = e.getName();
if (inToc && "a".equals(name))
if (EpubConstants.HtmlNamespaceUri.equals(e.getNamespace()) && e.getName().equals("a"))
{
context.featureReport.report(FeatureEnum.TOC_LINKS, parser.getLocation());
String href = e.getAttribute("href");
if (href != null)
{
String resolvedHref = PathUtil.resolveRelativeReference(base, href);
// Feature reporting
if (currentNavType == NavType.TOC)
{
context.featureReport.report(FeatureEnum.TOC_LINKS, parser.getLocation());
}

// For 'toc', 'landmarks', and 'page-list' nav:
// the link MUST resolve to a Top-level Content Document
// 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(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 All @@ -36,39 +94,56 @@ public void endElement()
{
super.endElement();
XMLElement e = parser.getCurrentElement();
String name = e.getName();
if (inToc && "nav".equals(name))
if (EpubConstants.HtmlNamespaceUri.equals(e.getNamespace()) && e.getName().equals("nav"))
{
inToc = false;
currentNavType = NavType.NONE;
}
}

@Override
protected void checkType(XMLElement e, String type)
{
isNavTypes = (EpubConstants.HtmlNamespaceUri.equals(e.getNamespace())
&& e.getName().equals("nav"));
super.checkType(e, type);
isNavTypes = false;
}

@Override
protected void checkTypes(Set<EPUB_TYPES> types)
{
super.checkTypes(types);
if (types.contains(EPUB_TYPES.TOC))
{
inToc = true;
}
if (types.contains(EPUB_TYPES.PAGE_LIST))
{
context.featureReport.report(FeatureEnum.PAGE_LIST, parser.getLocation());
}
if (types.contains(EPUB_TYPES.LOI))
{
context.featureReport.report(FeatureEnum.LOI, parser.getLocation());
}
if (types.contains(EPUB_TYPES.LOT))
{
context.featureReport.report(FeatureEnum.LOT, parser.getLocation());
}
if (types.contains(EPUB_TYPES.LOA))
{
context.featureReport.report(FeatureEnum.LOA, parser.getLocation());
}
if (types.contains(EPUB_TYPES.LOV))
if (isNavTypes)
{
context.featureReport.report(FeatureEnum.LOV, parser.getLocation());
if (types.contains(EPUB_TYPES.TOC))
{
currentNavType = NavType.TOC;
}
if (types.contains(EPUB_TYPES.PAGE_LIST))
{
currentNavType = NavType.PAGE_LIST;
context.featureReport.report(FeatureEnum.PAGE_LIST, parser.getLocation());
}
if (types.contains(EPUB_TYPES.LANDMARKS))
{
currentNavType = NavType.LANDMARKS;
}
if (types.contains(EPUB_TYPES.LOI))
{
context.featureReport.report(FeatureEnum.LOI, parser.getLocation());
}
if (types.contains(EPUB_TYPES.LOT))
{
context.featureReport.report(FeatureEnum.LOT, parser.getLocation());
}
if (types.contains(EPUB_TYPES.LOA))
{
context.featureReport.report(FeatureEnum.LOA, parser.getLocation());
}
if (types.contains(EPUB_TYPES.LOV))
{
context.featureReport.report(FeatureEnum.LOV, parser.getLocation());
}
}
}

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