Skip to content

Commit

Permalink
feat: update viewport meta element requirements
Browse files Browse the repository at this point in the history
This commit implements the post-CR changes on the requirements for the
viewport meta element used to define the ICB in fixed-layout documents.

Viewport syntax:
- properties can now have several values
- the `ViewportMeta` object now return a list of string values when
  looking up values for a name.

ICB definition checks:
- only the first `viewport` `meta` element of fixed-layout documents
  is checked
- new `HTM-059` error is reported when `height` or `width` dimensions
  are specified more than once
- new `HTM-060a` usage is reported for any subsequent `viewport` `meta`
  element found in fixed-layout documents
- new `HTM-060b` usage is reported fo any `viewport` `meta` element
  found in reflowable documents

Fix #1401, Fix #1449
  • Loading branch information
rdeltour committed Dec 13, 2022
1 parent 5fca49f commit 9f75a1d
Show file tree
Hide file tree
Showing 35 changed files with 396 additions and 113 deletions.
Expand Up @@ -142,6 +142,9 @@ private void initialize()
severities.put(MessageId.HTM_056, Severity.ERROR);
severities.put(MessageId.HTM_057, Severity.ERROR);
severities.put(MessageId.HTM_058, Severity.ERROR);
severities.put(MessageId.HTM_059, Severity.ERROR);
severities.put(MessageId.HTM_060a, Severity.USAGE);
severities.put(MessageId.HTM_060b, Severity.USAGE);

// Media
severities.put(MessageId.MED_001, Severity.SUPPRESSED);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/adobe/epubcheck/messages/MessageId.java
Expand Up @@ -136,6 +136,9 @@ public enum MessageId implements Comparable<MessageId>
HTM_056("HTM_056"),
HTM_057("HTM_057"),
HTM_058("HTM_058"),
HTM_059("HTM_059"),
HTM_060a("HTM_060a"),
HTM_060b("HTM_060b"),

// Messages associated with media (images, audio and video)
MED_001("MED-001"),
Expand Down
66 changes: 40 additions & 26 deletions src/main/java/com/adobe/epubcheck/ops/OPSHandler30.java
@@ -1,5 +1,6 @@
package com.adobe.epubcheck.ops;

import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
Expand All @@ -10,6 +11,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.w3c.epubcheck.constants.MIMEType;
import org.w3c.epubcheck.core.references.Reference;
Expand Down Expand Up @@ -763,17 +765,14 @@ protected void processMeta()
String name = e.getAttribute("name");
if ("viewport".equals(Strings.nullToEmpty(name).trim()))
{
// Mark the viewport as seen
// (used when checking the existence of viewport metadata)
hasViewport = true;
// For a fixed-layout documents:
if (context.opfItem.isPresent() && context.opfItem.get().isFixedLayout())
String content = e.getAttribute("content");
// For fixed-layout documents, check the first viewport meta element
if (!hasViewport && context.opfItem.isPresent() && context.opfItem.get().isFixedLayout())
{
String contentAttribute = e.getAttribute("content");

hasViewport = true;
// parse viewport metadata
List<ViewportMeta.ParseError> syntaxErrors = new LinkedList<>();
ViewportMeta viewport = ViewportMeta.parse(contentAttribute,
ViewportMeta viewport = ViewportMeta.parse(content,
new ViewportMeta.ErrorHandler()
{
@Override
Expand All @@ -785,31 +784,46 @@ public void error(ParseError error, int position)
if (!syntaxErrors.isEmpty())
{
// report any syntax error
report.message(MessageId.HTM_047, location(), contentAttribute);
report.message(MessageId.HTM_047, location(), content);
}
else
{
// check that viewport metadata has a valid width value
if (!viewport.hasProperty("width"))
{
report.message(MessageId.HTM_056, location(), "width");
}
else if (!ViewportMeta.isValidWidth(viewport.getValue("width")))
for (String property : Arrays.asList("width", "height"))
{
report.message(MessageId.HTM_057, location(), "width");
// check that viewport metadata has a valid width value
if (!viewport.hasProperty(property))
{
report.message(MessageId.HTM_056, location(), property);
}
else
{
List<String> values = viewport.getValues(property);
if (values.size() > 1)
{
report.message(MessageId.HTM_059, location(), property,
values.stream().map(v -> '"' + v + '"').collect(Collectors.joining(", ")));
}
if (!ViewportMeta.isValidProperty(property, values.get(0)))
{
report.message(MessageId.HTM_057, location(), property);
}
}
}

// check that viewport metadata has a valid height value
if (!viewport.hasProperty("height"))
{
report.message(MessageId.HTM_056, location(), "height");
}
else if (!ViewportMeta.isValidHeight(viewport.getValue("height")))
{
report.message(MessageId.HTM_057, location(), "height");
}
}

}
else
{
// Report ignored secondary viewport meta in fixed-layout documents
if (context.opfItem.isPresent() && context.opfItem.get().isFixedLayout())
{
report.message(MessageId.HTM_060a, location(), content);
}
// Report ignored viewport meta in reflowable documents
else
{
report.message(MessageId.HTM_060b, location(), content);
}
}
}
}
Expand Down
79 changes: 45 additions & 34 deletions src/main/java/org/w3c/epubcheck/util/microsyntax/ViewportMeta.java
Expand Up @@ -3,11 +3,12 @@
import static org.w3c.epubcheck.util.infra.InfraUtil.isASCIIWhitespace;

import java.nio.CharBuffer;
import java.util.Map;
import java.util.List;
import java.util.regex.Pattern;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;

public class ViewportMeta
{
Expand All @@ -19,16 +20,18 @@ public static ViewportMeta parse(String string, ErrorHandler errorHandler)
return new Parser(errorHandler).parse(string);
}

public static boolean isValidHeight(String height)
public static boolean isValidProperty(String name, String value)
{
Preconditions.checkArgument(height != null);
return VIEWPORT_HEIGHT_REGEX.matcher(height).matches();
}

public static boolean isValidWidth(String width)
{
Preconditions.checkArgument(width != null);
return VIEWPORT_WIDTH_REGEX.matcher(width).matches();
Preconditions.checkNotNull(value);
switch (Preconditions.checkNotNull(name))
{
case "width":
return VIEWPORT_WIDTH_REGEX.matcher(value).matches();
case "height":
return VIEWPORT_HEIGHT_REGEX.matcher(value).matches();
default:
return true;
}
}

public static enum ParseError
Expand All @@ -51,7 +54,8 @@ public interface ErrorHandler

private final static class Builder
{
public ImmutableMap.Builder<String, String> properties = ImmutableMap.builder();
public ImmutableListMultimap.Builder<String, String> properties = ImmutableListMultimap
.builder();

public ViewportMeta build()
{
Expand Down Expand Up @@ -123,40 +127,38 @@ public ViewportMeta parse(CharSequence string)
}
else if (c == '=' || isASCIIWhitespace(c))
{
if (name.length() == 0)
{
error(ParseError.NAME_EMPTY, input.position());
return builder.build();
}
state = State.ASSIGN;
consume = false;
}
else if (c == ',' || c == ';')
{
if (name.length() == 0)
{
error(ParseError.LEADING_SEPARATOR, input.position());
}
else
{
error(ParseError.VALUE_EMPTY, input.position());
}
return builder.build();
state = State.SEPARATOR;
consume = false;
}
else
{
name.append(c);
}
break;
case ASSIGN:
if (isASCIIWhitespace(c))
if (name.length()==0) {
// assign state but no name was found
error(ParseError.NAME_EMPTY, input.position());
return builder.build();
}
else if (isASCIIWhitespace(c))
{
// skip whitespace
}
else if (c == '=')
{
state = State.VALUE;
}
else if (c == ',' || c == ';')
{
state = State.SEPARATOR;
consume = false;
}
else
{
// no '=' was matched (i.e. no value is set)
Expand Down Expand Up @@ -200,6 +202,11 @@ else if (c == '=')
consume = false;
}
case SEPARATOR:
if (name.length() == 0)
{
error(ParseError.LEADING_SEPARATOR, input.position());
return builder.build();
}
if (c == ',' || c == ';' || isASCIIWhitespace(c))
{
// skip repeating separators
Expand All @@ -215,13 +222,12 @@ else if (c == '=')
break;
}
}
if (value.length() != 0)
{
builder.withProperty(name.toString(), value.toString());
}
else if (name.length() != 0)
// finalize, report if unexpected final state
if (state == State.VALUE && value.length() == 0)
{
error(ParseError.VALUE_EMPTY, input.position());
} else {
builder.withProperty(name.toString(), value.toString());
}
if (state == State.SEPARATOR)
{
Expand All @@ -231,7 +237,7 @@ else if (name.length() != 0)
}
}

private final Map<String, String> properties;
private final ImmutableListMultimap<String, String> properties;

private ViewportMeta(Builder builder)
{
Expand All @@ -243,9 +249,14 @@ public boolean hasProperty(String name)
return properties.containsKey(name);
}

public String getValue(String name)
public List<String> getValues(String name)
{
return properties.get(name);
}

public ListMultimap<String, String> asMultimap()
{
return properties;
}

}
Expand Up @@ -60,7 +60,10 @@ HTM_054=Custom attribute namespace ("%1$s") must not include the string "%2$s" i
HTM_055=The "%1$s" element should not be used (discouraged construct)
HTM_056=Viewport metadata has no "%1$s" dimension (both "width" and "height" properties are required)
HTM_057=Viewport "%1$s" value must be a positive number or the keyword "device-%1$s"
HTM_058=HTML documents must be encoded in UTF-8, but UTF-16 was detected.
HTM_058=HTML documents must be encoded in UTF-8, but UTF-16 was detected.
HTM_059=Viewport "%1$s" property must not be defined more than once, but found values [%2$s].
HTM_060a=EPUB reading systems must ignore secondary viewport meta elements in fixed-layout documents; viewport declaration "%1$s" will be ignored.
HTM_060b=EPUB reading systems must ignore viewport meta elements in reflowable documents; viewport declaration "%1$s" will be ignored.

#media
MED_003=Picture "img" elements must reference core media type resources, but found resource "%1$s" of type "%2$s".
Expand Down
Expand Up @@ -2,6 +2,7 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;

Expand All @@ -12,6 +13,7 @@
import org.w3c.epubcheck.util.microsyntax.ViewportMeta.ParseError;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;

import io.cucumber.java.ParameterType;
import io.cucumber.java.en.Then;
Expand All @@ -20,6 +22,8 @@
public class ViewportSteps
{

private ViewportMeta viewport;

public static final class TestErrorHandler implements ErrorHandler
{
public final List<ParseError> errors = new LinkedList<>();
Expand Down Expand Up @@ -58,7 +62,7 @@ public ParseError error(String error)
@When("parsing viewport {string}")
public void parseViewport(String content)
{
ViewportMeta.parse(content, handler);
viewport = ViewportMeta.parse(content, handler);
}

@Then("no error is returned")
Expand All @@ -67,6 +71,12 @@ public void assertValid()
assertThat("Unexpected errors", handler.errors(), is(empty()));
}

@Then("the parsed viewport equals {multimap}")
public void assertResult(ImmutableListMultimap<String, String> multimap)
{
assertThat(viewport.asMultimap(), is(equalTo(multimap)));
}

@Then("error {error} is returned")
public void assertError(ParseError error)
{
Expand Down
Expand Up @@ -2,8 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=600,height=1200" />
<meta name="viewport" content="width=600,height=1200" />
<meta name="viewport" content="width=600,height=1200,width=device-width,height=device-height" />
<title>Minimal EPUB</title>
</head>
<body>
Expand Down
Expand Up @@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=600,height=" />
<meta name="viewport" content="width=600,height" />
<title>Minimal EPUB</title>
</head>
<body>
Expand Down
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="inital-scale=1.0" />
<!-- ICB dimensions must be defined in the first viewport meta element -->
<meta name="viewport" content="width=600,height=600" />
<title>Minimal EPUB</title>
</head>
<body>
<h1>Loomings</h1>
<p>Call me Ishmael.</p>
</body>
</html>
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="en" lang="en">
<head>
<meta charset="utf-8"/>
<title>Minimal Nav</title>
</head>
<body>
<nav epub:type="toc">
<ol>
<li><a href="content_001.xhtml">content 001</a></li>
</ol>
</nav>
</body>
</html>

0 comments on commit 9f75a1d

Please sign in to comment.