Skip to content

Commit edcd253

Browse files
committed
feat: check required cardinality of meta properties
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
1 parent d1727d8 commit edcd253

28 files changed

+560
-65
lines changed

src/main/resources/com/adobe/epubcheck/schema/30/package-30.sch

Lines changed: 109 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
<ns uri="http://www.idpf.org/2007/opf" prefix="opf"/>
55
<ns uri="http://purl.org/dc/elements/1.1/" prefix="dc"/>
6+
7+
<!-- Unique ID checks -->
68

79
<pattern id="opf.uid">
810
<rule context="opf:package[@unique-identifier]">
@@ -27,25 +29,9 @@
2729
>dcterms:modified illegal syntax (expecting: "CCYY-MM-DDThh:mm:ssZ")</assert>
2830
</rule>
2931
</pattern>
30-
31-
<pattern id="opf.refines.relative">
32-
<rule context="*[@refines and starts-with(normalize-space(@refines),'#')][not(ancestor::opf:collection)]">
33-
<let name="refines-target-id" value="substring(normalize-space(@refines), 2)"/>
34-
<assert test="//*[normalize-space(@id)=$refines-target-id]">@refines missing target id: "<value-of
35-
select="$refines-target-id"/>"</assert>
36-
</rule>
37-
</pattern>
38-
39-
<pattern id="opf.meta.source-of">
40-
<rule context="opf:meta[normalize-space(@property)='source-of']">
41-
<assert test="normalize-space(.) eq 'pagination'">The "source-of" property must have the
42-
value "pagination"</assert>
43-
<assert
44-
test="exists(@refines) and exists(../dc:source[normalize-space(@id)=substring(normalize-space(current()/@refines),2)])"
45-
>The "source-of" property must refine a "dc:source" element.</assert>
46-
</rule>
47-
</pattern>
48-
32+
33+
<!-- Link checks -->
34+
4935
<pattern id="opf.link.record">
5036
<rule context="opf:link[tokenize(@rel,'\s+')='record']">
5137
<assert test="exists(@media-type)">The type of "record" references must be identifiable
@@ -61,6 +47,35 @@
6147
<assert test="exists(@refines)">"voicing" links must have a "refines" attribute.</assert>
6248
</rule>
6349
</pattern>
50+
51+
<!-- Metadata checks -->
52+
53+
<pattern id="opf.refines.relative">
54+
<rule context="*[@refines and starts-with(normalize-space(@refines),'#')][not(ancestor::opf:collection)]">
55+
<let name="refines-target-id" value="substring(normalize-space(@refines), 2)"/>
56+
<assert test="//*[normalize-space(@id)=$refines-target-id]">@refines missing target id: "<value-of
57+
select="$refines-target-id"/>"</assert>
58+
</rule>
59+
</pattern>
60+
61+
<pattern id="opf.dc.subject.authority-term">
62+
<rule context="opf:metadata/dc:subject">
63+
<let name="id" value="normalize-space(./@id)"/>
64+
<let name="authority" value="//opf:meta[normalize-space(@property)='authority'][substring(normalize-space(@refines), 2) = $id]"/>
65+
<let name="term" value="//opf:meta[normalize-space(@property)='term'][substring(normalize-space(@refines), 2) = $id]"/>
66+
<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>
67+
<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>
68+
<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>
69+
</rule>
70+
</pattern>
71+
72+
<pattern id="opf.meta.authority">
73+
<rule context="opf:meta[normalize-space(@property)='authority']">
74+
<assert test="exists(../dc:subject[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
75+
>Property "authority" must refine a "subject" property.</assert>
76+
<!-- Cardinality is checked in opf.dc.subject.authority-term -->
77+
</rule>
78+
</pattern>
6479

6580
<pattern id="opf.meta.belongs-to-collection">
6681
<rule context="opf:meta[normalize-space(@property)='belongs-to-collection']">
@@ -70,15 +85,86 @@
7085
properties.</assert>
7186
</rule>
7287
</pattern>
73-
88+
7489
<pattern id="opf.meta.collection-type">
7590
<rule context="opf:meta[normalize-space(@property)='collection-type']">
7691
<assert
7792
test="exists(../opf:meta[normalize-space(@id)=substring(normalize-space(current()/@refines),2)][normalize-space(@property)='belongs-to-collection'])"
7893
>Property "collection-type" must refine a "belongs-to-collection" property.</assert>
94+
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
95+
>Property "collection-type" cannot be declared more than once to refine the same "belongs-to-collection" expression.</report>
7996
</rule>
8097
</pattern>
81-
98+
99+
<pattern id="opf.meta.display-seq">
100+
<rule context="opf:meta[normalize-space(@property)='display-seq']">
101+
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
102+
>Property "display-seq" cannot be declared more than once to refine the same expression.</report>
103+
</rule>
104+
</pattern>
105+
106+
<pattern id="opf.meta.file-as">
107+
<rule context="opf:meta[normalize-space(@property)='file-as']">
108+
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
109+
>Property "file-as" cannot be declared more than once to refine the same expression.</report>
110+
</rule>
111+
</pattern>
112+
113+
<pattern id="opf.meta.group-position">
114+
<rule context="opf:meta[normalize-space(@property)='group-position']">
115+
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
116+
>Property "group-position" cannot be declared more than once to refine the same expression.</report>
117+
</rule>
118+
</pattern>
119+
120+
<pattern id="opf.meta.identifier-type">
121+
<rule context="opf:meta[normalize-space(@property)='identifier-type']">
122+
<assert test="exists(../(dc:identifier|dc:source)[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
123+
>Property "identifier-type" must refine an "identifier" or "source" property.</assert>
124+
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
125+
>Property "identifier-type" cannot be declared more than once to refine the same expression.</report>
126+
</rule>
127+
</pattern>
128+
129+
<pattern id="opf.meta.role">
130+
<rule context="opf:meta[normalize-space(@property)='role']">
131+
<assert test="exists(../(dc:creator|dc:contributor)[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
132+
>Property "role" must refine a "creator" or "contributor" property.</assert>
133+
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
134+
>Property "role" cannot be declared more than once to refine a single "creator" or "contributor" property.</report>
135+
</rule>
136+
</pattern>
137+
138+
<pattern id="opf.meta.source-of">
139+
<rule context="opf:meta[normalize-space(@property)='source-of']">
140+
<assert test="normalize-space(.) eq 'pagination'">The "source-of" property must have the
141+
value "pagination"</assert>
142+
<assert
143+
test="exists(@refines) and exists(../dc:source[normalize-space(@id)=substring(normalize-space(current()/@refines),2)])"
144+
>The "source-of" property must refine a "source" property.</assert>
145+
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
146+
>Property "source-of" cannot be declared more than once to refine the same "source" expression.</report>
147+
</rule>
148+
</pattern>
149+
150+
<pattern id="opf.meta.term">
151+
<rule context="opf:meta[normalize-space(@property)='term']">
152+
<assert test="exists(../dc:subject[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
153+
>Property "term" must refine a "subject" property.</assert>
154+
<!-- Cardinality is checked in opf.dc.subject.authority-term -->
155+
</rule>
156+
</pattern>
157+
158+
<pattern id="opf.meta.title-type">
159+
<rule context="opf:meta[normalize-space(@property)='title-type']">
160+
<assert test="exists(../dc:title[concat('#',normalize-space(@id)) = normalize-space(current()/@refines)])"
161+
>Property "title-type" must refine a "title" property.</assert>
162+
<report test="exists(preceding-sibling::opf:meta[normalize-space(@property) = normalize-space(current()/@property)][normalize-space(@refines) = normalize-space(current()/@refines)])"
163+
>Property "title-type" cannot be declared more than once to refine the same "title" expression.</report>
164+
</rule>
165+
</pattern>
166+
167+
<!-- Item checks -->
82168

83169
<pattern id="opf.itemref">
84170
<rule context="opf:spine/opf:itemref[@idref]">
@@ -162,6 +248,8 @@
162248
(number of "cover-image" items: <value-of select="count($item)"/>).</assert>
163249
</rule>
164250
</pattern>
251+
252+
<!-- Rendition properties checks -->
165253

166254
<pattern id="opf.rendition.globals">
167255
<rule context="opf:package/opf:metadata">
@@ -337,17 +425,6 @@
337425
</rule>
338426
</pattern>
339427

340-
<pattern id="opf.subject.authority-term">
341-
<rule context="opf:metadata/dc:subject">
342-
<let name="id" value="normalize-space(./@id)"/>
343-
<let name="authority" value="//opf:meta[normalize-space(@property)='authority'][substring(normalize-space(@refines), 2) = $id]"/>
344-
<let name="term" value="//opf:meta[normalize-space(@property)='term'][substring(normalize-space(@refines), 2) = $id]"/>
345-
<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>
346-
<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>
347-
<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>
348-
</rule>
349-
</pattern>
350-
351428
<!-- EPUB 3.2 Deprecated Features -->
352429

353430
<pattern id="opf.bindings.deprecated">
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
3+
xmlns:dc="http://purl.org/dc/elements/1.1/">
4+
<metadata>
5+
<dc:title id="title">Title</dc:title>
6+
<dc:language>en</dc:language>
7+
<dc:identifier id="uid">NOID</dc:identifier>
8+
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
9+
<meta refines="#title" property="authority">something</meta>
10+
</metadata>
11+
<manifest>
12+
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
13+
</manifest>
14+
<spine>
15+
<itemref idref="t001"/>
16+
</spine>
17+
</package>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
3+
xmlns:dc="http://purl.org/dc/elements/1.1/">
4+
<metadata>
5+
<dc:title>Title</dc:title>
6+
<dc:language>en</dc:language>
7+
<dc:identifier id="uid">NOID</dc:identifier>
8+
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
9+
<!-- Collection metadata -->
10+
<meta property="belongs-to-collection" id="c01">A Collection</meta>
11+
<meta property="belongs-to-collection" id="c02" refines="#c01">A Super Collection</meta>
12+
<meta refines="#c01" property="collection-type">set</meta>
13+
<meta refines="#c01" property="collection-type">set</meta>
14+
<meta refines="#c02" property="collection-type">set</meta>
15+
</metadata>
16+
<manifest>
17+
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
18+
</manifest>
19+
<spine>
20+
<itemref idref="t001"/>
21+
</spine>
22+
</package>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
3+
xmlns:dc="http://purl.org/dc/elements/1.1/">
4+
<metadata>
5+
<dc:title>Title</dc:title>
6+
<dc:language>en</dc:language>
7+
<dc:identifier id="uid">NOID</dc:identifier>
8+
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
9+
<dc:creator id="creator">Me</dc:creator>
10+
<meta refines="#creator" property="display-seq">1</meta>
11+
<meta refines="#creator" property="display-seq">2</meta>
12+
</metadata>
13+
<manifest>
14+
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
15+
</manifest>
16+
<spine>
17+
<itemref idref="t001"/>
18+
</spine>
19+
</package>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
3+
xmlns:dc="http://purl.org/dc/elements/1.1/">
4+
<metadata>
5+
<dc:title>Title</dc:title>
6+
<dc:language>en</dc:language>
7+
<dc:identifier id="uid">NOID</dc:identifier>
8+
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
9+
<dc:creator id="creator">Me</dc:creator>
10+
<meta refines="#creator" property="file-as">Me</meta>
11+
<meta refines="#creator" property="file-as">Also Me</meta>
12+
</metadata>
13+
<manifest>
14+
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
15+
</manifest>
16+
<spine>
17+
<itemref idref="t001"/>
18+
</spine>
19+
</package>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
3+
xmlns:dc="http://purl.org/dc/elements/1.1/">
4+
<metadata>
5+
<dc:title>Title</dc:title>
6+
<dc:language>en</dc:language>
7+
<dc:identifier id="uid">NOID</dc:identifier>
8+
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
9+
<dc:creator id="creator">Me</dc:creator>
10+
<meta refines="#creator" property="file-as">Me</meta>
11+
</metadata>
12+
<manifest>
13+
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
14+
</manifest>
15+
<spine>
16+
<itemref idref="t001"/>
17+
</spine>
18+
</package>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"
3+
xmlns:dc="http://purl.org/dc/elements/1.1/">
4+
<metadata>
5+
<dc:title>Title</dc:title>
6+
<dc:language>en</dc:language>
7+
<dc:identifier id="uid">NOID</dc:identifier>
8+
<meta property="dcterms:modified">2019-01-01T12:00:00Z</meta>
9+
<meta property="belongs-to-collection" id="c01">collection</meta>
10+
<meta refines="#c01" property="group-position">1</meta>
11+
<meta refines="#c01" property="group-position">2</meta>
12+
</metadata>
13+
<manifest>
14+
<item id="t001" href="contents.xhtml" properties="nav" media-type="application/xhtml+xml"/>
15+
</manifest>
16+
<spine>
17+
<itemref idref="t001"/>
18+
</spine>
19+
</package>

0 commit comments

Comments
 (0)