Skip to content

Commit

Permalink
feat: check required cardinality of meta properties
Browse files Browse the repository at this point in the history
The cardinality of refining properties defined with the `meta` element
is defined in Appendix C "Meta Properties Vocabulary":
  https://www.w3.org/publishing/epub32/epub-packages.html#app-meta-property-vocab

Summary:
- add schematron rules to check that properties defined as
"zero or one" are not defined more than once
- add test for all properties
- rename some existing tests for consistency

Fixes #1121
  • Loading branch information
rdeltour committed Feb 26, 2021
1 parent d1727d8 commit edcd253
Show file tree
Hide file tree
Showing 28 changed files with 560 additions and 65 deletions.
141 changes: 109 additions & 32 deletions src/main/resources/com/adobe/epubcheck/schema/30/package-30.sch
Expand Up @@ -3,6 +3,8 @@

<ns uri="http://www.idpf.org/2007/opf" prefix="opf"/>
<ns uri="http://purl.org/dc/elements/1.1/" prefix="dc"/>

<!-- Unique ID checks -->

<pattern id="opf.uid">
<rule context="opf:package[@unique-identifier]">
Expand All @@ -27,25 +29,9 @@
>dcterms:modified illegal syntax (expecting: "CCYY-MM-DDThh:mm:ssZ")</assert>
</rule>
</pattern>

<pattern id="opf.refines.relative">
<rule context="*[@refines and starts-with(normalize-space(@refines),'#')][not(ancestor::opf:collection)]">
<let name="refines-target-id" value="substring(normalize-space(@refines), 2)"/>
<assert test="//*[normalize-space(@id)=$refines-target-id]">@refines missing target id: "<value-of
select="$refines-target-id"/>"</assert>
</rule>
</pattern>

<pattern id="opf.meta.source-of">
<rule context="opf:meta[normalize-space(@property)='source-of']">
<assert test="normalize-space(.) eq 'pagination'">The "source-of" property must have the
value "pagination"</assert>
<assert
test="exists(@refines) and exists(../dc:source[normalize-space(@id)=substring(normalize-space(current()/@refines),2)])"
>The "source-of" property must refine a "dc:source" element.</assert>
</rule>
</pattern>


<!-- Link checks -->

<pattern id="opf.link.record">
<rule context="opf:link[tokenize(@rel,'\s+')='record']">
<assert test="exists(@media-type)">The type of "record" references must be identifiable
Expand All @@ -61,6 +47,35 @@
<assert test="exists(@refines)">"voicing" links must have a "refines" attribute.</assert>
</rule>
</pattern>

<!-- Metadata checks -->

<pattern id="opf.refines.relative">
<rule context="*[@refines and starts-with(normalize-space(@refines),'#')][not(ancestor::opf:collection)]">
<let name="refines-target-id" value="substring(normalize-space(@refines), 2)"/>
<assert test="//*[normalize-space(@id)=$refines-target-id]">@refines missing target id: "<value-of
select="$refines-target-id"/>"</assert>
</rule>
</pattern>

<pattern id="opf.dc.subject.authority-term">
<rule context="opf:metadata/dc:subject">
<let name="id" value="normalize-space(./@id)"/>
<let name="authority" value="//opf:meta[normalize-space(@property)='authority'][substring(normalize-space(@refines), 2) = $id]"/>
<let name="term" value="//opf:meta[normalize-space(@property)='term'][substring(normalize-space(@refines), 2) = $id]"/>
<report test="(count($authority) = 1 and count($term) = 0)">A term property must be associated with a dc:subject when an authority is specified</report>
<report test="(count($authority) = 0 and count($term) = 1)">An authority property must be associated with a dc:subject when a term is specified</report>
<report test="(count($authority) &gt; 1 or count($term) &gt; 1)">Only one pair of authority and term properties can be associated with a dc:subject</report>
</rule>
</pattern>

<pattern id="opf.meta.authority">
<rule context="opf:meta[normalize-space(@property)='authority']">
<assert test="exists(../dc:subject[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
>Property "authority" must refine a "subject" property.</assert>
<!-- Cardinality is checked in opf.dc.subject.authority-term -->
</rule>
</pattern>

<pattern id="opf.meta.belongs-to-collection">
<rule context="opf:meta[normalize-space(@property)='belongs-to-collection']">
Expand All @@ -70,15 +85,86 @@
properties.</assert>
</rule>
</pattern>

<pattern id="opf.meta.collection-type">
<rule context="opf:meta[normalize-space(@property)='collection-type']">
<assert
test="exists(../opf:meta[normalize-space(@id)=substring(normalize-space(current()/@refines),2)][normalize-space(@property)='belongs-to-collection'])"
>Property "collection-type" must refine a "belongs-to-collection" property.</assert>
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
>Property "collection-type" cannot be declared more than once to refine the same "belongs-to-collection" expression.</report>
</rule>
</pattern>


<pattern id="opf.meta.display-seq">
<rule context="opf:meta[normalize-space(@property)='display-seq']">
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
>Property "display-seq" cannot be declared more than once to refine the same expression.</report>
</rule>
</pattern>

<pattern id="opf.meta.file-as">
<rule context="opf:meta[normalize-space(@property)='file-as']">
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
>Property "file-as" cannot be declared more than once to refine the same expression.</report>
</rule>
</pattern>

<pattern id="opf.meta.group-position">
<rule context="opf:meta[normalize-space(@property)='group-position']">
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
>Property "group-position" cannot be declared more than once to refine the same expression.</report>
</rule>
</pattern>

<pattern id="opf.meta.identifier-type">
<rule context="opf:meta[normalize-space(@property)='identifier-type']">
<assert test="exists(../(dc:identifier|dc:source)[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
>Property "identifier-type" must refine an "identifier" or "source" property.</assert>
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
>Property "identifier-type" cannot be declared more than once to refine the same expression.</report>
</rule>
</pattern>

<pattern id="opf.meta.role">
<rule context="opf:meta[normalize-space(@property)='role']">
<assert test="exists(../(dc:creator|dc:contributor)[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
>Property "role" must refine a "creator" or "contributor" property.</assert>
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
>Property "role" cannot be declared more than once to refine a single "creator" or "contributor" property.</report>
</rule>
</pattern>

<pattern id="opf.meta.source-of">
<rule context="opf:meta[normalize-space(@property)='source-of']">
<assert test="normalize-space(.) eq 'pagination'">The "source-of" property must have the
value "pagination"</assert>
<assert
test="exists(@refines) and exists(../dc:source[normalize-space(@id)=substring(normalize-space(current()/@refines),2)])"
>The "source-of" property must refine a "source" property.</assert>
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
>Property "source-of" cannot be declared more than once to refine the same "source" expression.</report>
</rule>
</pattern>

<pattern id="opf.meta.term">
<rule context="opf:meta[normalize-space(@property)='term']">
<assert test="exists(../dc:subject[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
>Property "term" must refine a "subject" property.</assert>
<!-- Cardinality is checked in opf.dc.subject.authority-term -->
</rule>
</pattern>

<pattern id="opf.meta.title-type">
<rule context="opf:meta[normalize-space(@property)='title-type']">
<assert test="exists(../dc:title[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
>Property "title-type" must refine a "title" property.</assert>
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
>Property "title-type" cannot be declared more than once to refine the same "title" expression.</report>
</rule>
</pattern>

<!-- Item checks -->

<pattern id="opf.itemref">
<rule context="opf:spine/opf:itemref[@idref]">
Expand Down Expand Up @@ -162,6 +248,8 @@
(number of "cover-image" items: <value-of select="count($item)"/>).</assert>
</rule>
</pattern>

<!-- Rendition properties checks -->

<pattern id="opf.rendition.globals">
<rule context="opf:package/opf:metadata">
Expand Down Expand Up @@ -337,17 +425,6 @@
</rule>
</pattern>

<pattern id="opf.subject.authority-term">
<rule context="opf:metadata/dc:subject">
<let name="id" value="normalize-space(./@id)"/>
<let name="authority" value="//opf:meta[normalize-space(@property)='authority'][substring(normalize-space(@refines), 2) = $id]"/>
<let name="term" value="//opf:meta[normalize-space(@property)='term'][substring(normalize-space(@refines), 2) = $id]"/>
<report test="(count($authority) = 1 and count($term) = 0)">A term property must be associated with a dc:subject when an authority is specified</report>
<report test="(count($authority) = 0 and count($term) = 1)">An authority property must be associated with a dc:subject when a term is specified</report>
<report test="(count($authority) &gt; 1 or count($term) &gt; 1)">Only one pair of authority and term properties can be associated with a dc:subject</report>
</rule>
</pattern>

<!-- EPUB 3.2 Deprecated Features -->

<pattern id="opf.bindings.deprecated">
Expand Down
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title id="title">Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<meta refines="#title" property="authority">something</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title>Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<!-- Collection metadata -->
<meta property="belongs-to-collection" id="c01">A Collection</meta>
<meta property="belongs-to-collection" id="c02" refines="#c01">A Super Collection</meta>
<meta refines="#c01" property="collection-type">set</meta>
<meta refines="#c01" property="collection-type">set</meta>
<meta refines="#c02" property="collection-type">set</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title>Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<dc:creator id="creator">Me</dc:creator>
<meta refines="#creator" property="display-seq">1</meta>
<meta refines="#creator" property="display-seq">2</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title>Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<dc:creator id="creator">Me</dc:creator>
<meta refines="#creator" property="file-as">Me</meta>
<meta refines="#creator" property="file-as">Also Me</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title>Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<dc:creator id="creator">Me</dc:creator>
<meta refines="#creator" property="file-as">Me</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title>Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<meta property="belongs-to-collection" id="c01">collection</meta>
<meta refines="#c01" property="group-position">1</meta>
<meta refines="#c01" property="group-position">2</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title>Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<meta property="belongs-to-collection" id="c01">collection</meta>
<meta refines="#c01" property="group-position">1</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title>Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<meta property="identifier-type" refines="#uid" scheme="onix:codelist5">06</meta>
<meta property="identifier-type" refines="#uid" scheme="onix:codelist5">15</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title id="title">Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<meta property="identifier-type" refines="#title" scheme="onix:codelist5">06</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<dc:title>Title</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="uid">NOID</dc:identifier>
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
<meta property="identifier-type" refines="#uid" scheme="onix:codelist5">06</meta>
</metadata>
<manifest>
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="t001"/>
</spine>
</package>

0 comments on commit edcd253

Please sign in to comment.