|
2 | 2 | <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
|
3 | 3 | <ns uri="http://www.idpf.org/2007/ops" prefix="epub"/>
|
4 | 4 | <ns uri="http://www.w3.org/1999/xhtml" prefix="html"/>
|
5 |
| - |
| 5 | + |
6 | 6 | <!-- following variable declarations are used to test h# nesting depth -->
|
7 | 7 | <!-- checks if the body contains anything other than a single section or article - i.e., is it an implied section
|
8 | 8 | - previously tested if >1 article/section children with : or count(//html:body/child::html:*[self::html:article or self::html:section]) > 1
|
9 | 9 | but ambiguous whether multiple section elements is an implied body or just a weird breakup of the file
|
10 | 10 | -->
|
11 |
| - <let name="body-is-section" value="exists(//html:body/(html:* except (html:article|html:section)))"/> |
12 |
| - |
| 11 | + <let name="body-is-section" |
| 12 | + value="exists(//html:body/(html:* except (html:article | html:section)))"/> |
| 13 | + |
13 | 14 | <!-- check if implied heading -->
|
14 | 15 | <let name="body-label-len" value="string-length(normalize-space(//html:body/@aria-label))"/>
|
15 |
| - |
| 16 | + |
16 | 17 | <!-- finds the topmost heading in the file that is the descendant of the body (not sectioning element ancestors) or the first descendant of a section or article -->
|
17 |
| - <let name="topmost-heading" value="//html:body//(html:h1|html:h2|html:h3|html:h4|html:h5|html:h6)[not(ancestor::html:aside|ancestor::html:nav) and count(ancestor::html:section|ancestor::html:article) le 1]"/> |
18 |
| - |
| 18 | + <let name="topmost-heading" |
| 19 | + value="//html:body//(html:h1 | html:h2 | html:h3 | html:h4 | html:h5 | html:h6 | html:*[@role = 'heading'])[not(ancestor::html:aside | ancestor::html:nav) and count(ancestor::html:section | ancestor::html:article) le 1]"/> |
| 20 | + |
19 | 21 | <!-- extract the starting rank from the topmost-heading -->
|
20 |
| - <let name="topmost-heading-rank" value="if ($body-label-len > 0) then 1 else if (exists($topmost-heading)) then number(substring(name($topmost-heading[1]),2)) else 1"/> |
21 |
| - |
| 22 | + <let name="topmost-heading-rank" value=" |
| 23 | + if ($body-label-len > 0) then |
| 24 | + 1 |
| 25 | + else |
| 26 | + if (exists($topmost-heading)) then |
| 27 | + if ($topmost-heading[1][@role = 'heading']) then |
| 28 | + if ($topmost-heading[1]/@aria-level) then |
| 29 | + number($topmost-heading[1]/@aria-level) |
| 30 | + else |
| 31 | + 2 |
| 32 | + else |
| 33 | + number(substring(name($topmost-heading[1]), 2)) |
| 34 | + else |
| 35 | + 1"/> |
| 36 | + |
22 | 37 | <!-- find the nesting depth of the topmost heading (0 if body, 1 if a section or article around it) -->
|
23 |
| - <let name="topmost-heading-nest" value="if ($body-label-len > 0) then 0 else if (empty($topmost-heading[1]/(ancestor::html:section|ancestor::html:article|ancestor::html:nav))) then 0 else 1"/> |
24 |
| - |
25 |
| - |
| 38 | + <let name="topmost-heading-nest" value=" |
| 39 | + if ($body-label-len > 0) then |
| 40 | + 0 |
| 41 | + else |
| 42 | + if (empty($topmost-heading[1]/(ancestor::html:section | ancestor::html:article | ancestor::html:nav))) then |
| 43 | + 0 |
| 44 | + else |
| 45 | + 1"/> |
| 46 | + |
| 47 | + |
26 | 48 | <pattern id="edupub.headings">
|
27 |
| - <rule context="html:body[html:* except (html:article|html:section|html:aside|html:nav)]"> |
28 |
| - <let name="headings" value=".//(html:h1|html:h2|html:h3|html:h4|html:h5|html:h6)[empty(ancestor::html:section|ancestor::html:aside|ancestor::html:article|ancestor::html:nav)]"/> |
29 |
| - |
| 49 | + <rule context="html:body[html:* except (html:article | html:section | html:aside | html:nav)]"> |
| 50 | + <let name="headings" |
| 51 | + value=".//(html:h1 | html:h2 | html:h3 | html:h4 | html:h5 | html:h6 | html:*[@role = 'heading'])[empty(ancestor::html:section | ancestor::html:aside | ancestor::html:article | ancestor::html:nav)]"/> |
| 52 | + |
30 | 53 | <report test="@aria-label and $body-label-len = 0">Empty aria-label attribute found.</report>
|
31 |
| - |
32 |
| - <assert test="$body-label-len > 0 or count($headings) > 0">The body element requires a heading when it is used as an implied section.</assert> |
33 |
| - |
| 54 | + |
| 55 | + <assert test="$body-label-len > 0 or count($headings) > 0">The body element requires a |
| 56 | + heading when it is used as an implied section.</assert> |
| 57 | + |
34 | 58 | <!-- <report test="$arialabel-len > 0 and count($headings) > 0">The aria-label attribute must not be mixed with ranked headings.</report> -->
|
35 |
| - |
36 |
| - <report test="count($headings) > 1">More than one ranked heading found as direct descendant of body.</report> |
37 |
| - |
38 |
| - <report test="count($headings) = 1 and string-length(normalize-space(string-join($headings|$headings/html:img/@alt|$headings//@aria-label))) = 0">Empty ranked heading detected.</report> |
39 |
| - |
40 |
| - <report test="@aria-label and (normalize-space($headings) = normalize-space(@aria-label))">The value of the "aria-label" attribute must not be the same as the content of the heading.</report> |
| 59 | + |
| 60 | + <report test="count($headings) > 1">More than one ranked heading found as direct descendant |
| 61 | + of body.</report> |
| 62 | + |
| 63 | + <report |
| 64 | + test="count($headings) = 1 and string-length(normalize-space(string-join($headings | $headings/html:img/@alt | $headings//@aria-label))) = 0" |
| 65 | + >Empty ranked heading detected.</report> |
| 66 | + |
| 67 | + <report test="@aria-label and (normalize-space($headings) = normalize-space(@aria-label))">The |
| 68 | + value of the "aria-label" attribute must not be the same as the content of the |
| 69 | + heading.</report> |
41 | 70 | </rule>
|
42 |
| - |
43 |
| - |
44 |
| - <rule context="html:section|html:article"> |
| 71 | + |
| 72 | + |
| 73 | + <rule context="html:section | html:article"> |
45 | 74 | <let name="arialabel-len" value="string-length(normalize-space(@aria-label))"/>
|
46 |
| - <let name="headings" value=".//(html:h1|html:h2|html:h3|html:h4|html:h5|html:h6)[(ancestor::html:section|ancestor::html:article|ancestor::html:aside|ancestor::html:nav)[last()] = current()]"/> |
47 |
| - |
| 75 | + <let name="headings" |
| 76 | + value=".//(html:h1 | html:h2 | html:h3 | html:h4 | html:h5 | html:h6 | html:*[@role = 'heading'])[(ancestor::html:section | ancestor::html:article | ancestor::html:aside | ancestor::html:nav)[last()] = current()]"/> |
| 77 | + |
48 | 78 | <report test="@aria-label and $arialabel-len = 0">Empty aria-label attribute found.</report>
|
49 |
| - |
50 |
| - <assert test="$arialabel-len > 0 or count($headings) > 0"><value-of select="name()"/> does not have a heading.</assert> |
51 |
| - |
| 79 | + |
| 80 | + <assert test="$arialabel-len > 0 or count($headings) > 0"><value-of select="name()"/> |
| 81 | + does not have a heading.</assert> |
| 82 | + |
52 | 83 | <!-- <report test="$arialabel-len > 0 and count($headings) > 0">The aria-label attribute must not be mixed with ranked headings.</report> -->
|
53 |
| - |
54 |
| - <report test="count($headings) > 1">More than one ranked heading found as direct descendant of <value-of select="name()"/>.</report> |
55 |
| - |
56 |
| - <report test="count($headings) = 1 and string-length(normalize-space(string-join($headings|$headings/html:img/@alt|$headings//@aria-label))) = 0">Empty ranked heading detected.</report> |
57 |
| - |
58 |
| - <report test="@aria-label and (normalize-space($headings) = normalize-space(@aria-label))">The value of the "aria-label" attribute must not be the same as the content of the heading.</report> |
| 84 | + |
| 85 | + <report test="count($headings) > 1">More than one ranked heading found as direct descendant |
| 86 | + of <value-of select="name()"/>.</report> |
| 87 | + |
| 88 | + <report |
| 89 | + test="count($headings) = 1 and string-length(normalize-space(string-join($headings | $headings/html:img/@alt | $headings//@aria-label))) = 0" |
| 90 | + >Empty ranked heading detected.</report> |
| 91 | + |
| 92 | + <report test="@aria-label and (normalize-space($headings) = normalize-space(@aria-label))">The |
| 93 | + value of the "aria-label" attribute must not be the same as the content of the |
| 94 | + heading.</report> |
59 | 95 | </rule>
|
60 |
| - |
61 |
| - <rule context="html:h1|html:h2|html:h3|html:h4|html:h5|html:h6"> |
| 96 | + |
| 97 | + <rule |
| 98 | + context="html:h1 | html:h2 | html:h3 | html:h4 | html:h5 | html:h6 | html:*[@role = 'heading']"> |
62 | 99 | <!-- get the # from the h# tag found -->
|
63 |
| - <let name="current-rank" value="number(substring(name(current()),2))"/> |
64 |
| - |
| 100 | + <let name="current-rank" value=" |
| 101 | + if (current()[@role = 'heading']) then |
| 102 | + if (current()/@aria-level) then |
| 103 | + number(current()/@aria-level) |
| 104 | + else |
| 105 | + 2 |
| 106 | + else |
| 107 | + number(substring(name(current()), 2))"/> |
| 108 | + |
65 | 109 | <!-- find nesting depth -->
|
66 |
| - <let name="current-nesting" value="count(ancestor::html:section|ancestor::html:article|ancestor::html:aside|ancestor::html:nav)"/> |
67 |
| - |
| 110 | + <let name="current-nesting" |
| 111 | + value="count(ancestor::html:section | ancestor::html:article | ancestor::html:aside | ancestor::html:nav)"/> |
| 112 | + |
68 | 113 | <!-- derive the expected rank of this heading from the implied body or sectioning -->
|
69 |
| - <let name="expected-rank" value="if ($body-is-section) then $topmost-heading-rank - $topmost-heading-nest + $current-nesting else $topmost-heading-rank + $current-nesting - 1"/> |
70 |
| - |
| 114 | + <let name="expected-rank" value=" |
| 115 | + if ($body-is-section) then |
| 116 | + $topmost-heading-rank - $topmost-heading-nest + $current-nesting |
| 117 | + else |
| 118 | + $topmost-heading-rank + $current-nesting - 1"/> |
| 119 | + |
71 | 120 | <!-- report ranked headings in sectioning roots -->
|
72 |
| - <report test="ancestor::html:figure or ancestor::html:blockquote">Ranked headings are not valid in figure or blockquote</report> |
73 |
| - |
| 121 | + <report test="ancestor::html:figure or ancestor::html:blockquote">Ranked headings are not |
| 122 | + valid in figure or blockquote</report> |
| 123 | + |
74 | 124 | <!-- if the expected rank is below 6, check that it matches what is expected -->
|
75 |
| - <report test="$expected-rank < 6 and not($current-rank = $expected-rank)">The heading rank h<value-of select="$current-rank"/> does not match the current nesting level (<value-of select="$expected-rank"/>).</report> |
76 |
| - |
| 125 | + <report test="$expected-rank < 6 and not($current-rank = $expected-rank)">The heading rank |
| 126 | + h<value-of select="$current-rank"/> does not match the current nesting level (<value-of |
| 127 | + select="$expected-rank"/>).</report> |
| 128 | + |
77 | 129 | <!-- otherwise, just stop testing after 5 and report any headings that aren't six, since no higher exist -->
|
78 |
| - <report test="$expected-rank > 5 and $current-rank < 6">The current heading rank should be h6.</report> |
| 130 | + <report test="$expected-rank > 5 and $current-rank < 6">The current heading rank should |
| 131 | + be h6.</report> |
79 | 132 | </rule>
|
80 | 133 | </pattern>
|
81 |
| - |
| 134 | + |
82 | 135 | <pattern id="edupub.sectioning">
|
83 | 136 | <rule context="*[parent::html:body or parent::html:section][not(self::html:section)]">
|
84 |
| - <report test="preceding-sibling::html:section">Non-section elements not allowed between or after section elements.</report> |
| 137 | + <report test="preceding-sibling::html:section">Non-section elements not allowed between or |
| 138 | + after section elements.</report> |
85 | 139 | </rule>
|
86 | 140 | </pattern>
|
87 |
| - |
| 141 | + |
88 | 142 | <pattern id="edupub.subtitles">
|
89 |
| - <rule context="html:p[@epub:type='subtitle'][preceding-sibling::*[self::html:h1|self::html:h2|self::html:h3|self::html:h4|self::html:h5|self::html:h6]]"> |
90 |
| - <assert test="ancestor::html:header">Section subtitles must be wrapped in a header element.</assert> |
| 143 | + <rule |
| 144 | + context="html:p[@epub:type = 'subtitle'][preceding-sibling::*[self::html:h1 | self::html:h2 | self::html:h3 | self::html:h4 | self::html:h5 | self::html:h6]]"> |
| 145 | + <assert test="ancestor::html:header">Section subtitles must be wrapped in a header |
| 146 | + element.</assert> |
91 | 147 | </rule>
|
92 | 148 | </pattern>
|
93 | 149 | </schema>
|
0 commit comments