Download all files mentioned on this page or the files for each individual example:
The XML instance is a HTML document which contains a violation against the W3C Recommendation XHTML. In XHTML, the language should be set uniformly for all elements by using the lang and the xml:lang attribute.
1 | <?xml-model href="quickFix1.sch" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?> |
2 | <html xmlns="http://www.w3.org/1999/xhtml"> |
3 | <head> |
4 | <title>Citations Niccolò Machiavelli</title> |
5 | </head> |
6 | <body> |
7 | <h1>Citations <span lang="it">Niccolò Machiavelli</span></h1> |
8 | <p>Wars begin when you will, but they do not end when you please.</p> |
9 | <p lang="it" xml:lang="en">Può la disciplina nella guerra più che il furore.</p> |
10 | </body> |
11 | </html> |
The Schematron schema proves that:
the language is set for all elements.
each language declaration has been made with both language attributes (lang and xml:lang).
the language attributes have the same value in each element.
1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2"> |
3 | <ns uri="http://www.w3.org/1999/xhtml" prefix="html"/> |
4 | <title>Two different language attributes.</title> |
5 | <pattern> |
6 | <rule context="html:html"> |
7 | <assert test="@lang">The attribute "lang" is missing.</assert> |
8 | <assert test="@xml:lang">The attribute "xml:lang" is missing.</assert> |
9 | <assert test="@lang = @xml:lang"> The attributes "lang" and "xml:lang" should have the same value.</assert> |
10 | </rule> |
11 | <rule context="*[@xml:lang and @lang]"> |
12 | <assert test="@lang = @xml:lang" |
13 | > The attributes "lang" and "xml:lang" should have the same value.</assert> |
14 | </rule> |
15 | <rule context="*[@lang]"> |
16 | <assert test="@xml:lang" |
17 | >The attribute "xml:lang" is missing.</assert> |
18 | </rule> |
19 | <rule context="*[@xml:lang]"> |
20 | <assert test="@lang">The attribute "lang" is missing.</assert> |
21 | </rule> |
22 | </pattern> |
23 | </schema> |
7 | <assert test="@lang" sqf:fix="lang lang_xml_lang">The attribute "lang" is missing.</assert> |
8 | <assert test="@xml:lang" sqf:fix="xml_lang lang_xml_lang">The attribute "xml:lang" is missing.</assert> |
9 | <assert test="@lang = @xml:lang" sqf:fix="lang xml_lang"> The attributes "lang" and "xml:lang" should have the same value.</assert> |
[...] | |
12 | <assert test="@lang = @xml:lang" sqf:fix="lang xml_lang" |
13 | > The attributes "lang" and "xml:lang" should have the same value.</assert> |
[...] | |
16 | <assert test="@xml:lang" sqf:fix="xml_lang" |
17 | >The attribute "xml:lang" is missing.</assert> |
[...] | |
20 | <assert test="@lang" sqf:fix="lang">The attribute "lang" is missing.</assert> |
[...] | |
23 | <sqf:fixes> |
24 | <sqf:fix id="lang" use-when="@xml:lang"> |
25 | <sqf:description> |
26 | <sqf:title>Adds a "lang" attribute with the value "<value-of select="@xml:lang"/>".</sqf:title> |
27 | </sqf:description> |
28 | <sqf:add match="." target="lang" node-type="attribute"> |
29 | <value-of select="@xml:lang"/> |
30 | </sqf:add> |
31 | </sqf:fix> |
32 | <sqf:fix id="xml_lang" use-when="@lang" role="replace"> |
33 | <sqf:description> |
34 | <sqf:title>Adds a "xml:lang" attribute with the value "<value-of select="@lang"/>".</sqf:title> |
35 | </sqf:description> |
36 | <sqf:add match="." target="xml:lang" node-type="attribute"> |
37 | <value-of select="@lang"/> |
38 | </sqf:add> |
39 | </sqf:fix> |
40 | <sqf:fix id="lang_xml_lang"> |
41 | <sqf:description> |
42 | <sqf:title>Adds a "xml:lang" and a "lang" attribute to the node.</sqf:title> |
43 | </sqf:description> |
44 | <sqf:user-entry name="lang" type="xs:string"> |
45 | <sqf:description> |
46 | <sqf:title>Enter the correct language code.</sqf:title> |
47 | </sqf:description> |
48 | </sqf:user-entry> |
49 | <sqf:add match="." target="xml:lang" node-type="attribute"><value-of select="$lang"/></sqf:add> |
50 | <sqf:add match="." target="lang" node-type="attribute"><value-of select="$lang"/></sqf:add> |
51 | </sqf:fix> |
52 | </sqf:fixes> |
53 | </schema> |
The schema contains three QuickFixes:
The first QuickFix should be used if the context node has a xml:lang but no lang attribute or a lang attribute with a wrong value.
It uses the activity element <sqf:add> which adds a lang attribute with the value of the xml:lang attribute to the context node. If the context node has already a lang attribute, it will be overwritten.
The second QuickFix should be used if the context node has a lang but no xml:lang attribute or a xml:lang attribute with a wrong value.
It uses the activity element <sqf:add> which adds a xml:lang attribute with the value of the lang attribute to the context node. If the context node has already a xml:lang attribute, it will be overwritten.
The third QuickFix should be used if the context node has no language attribute or a language attribute with a wrong value. The correct value can be set by the user with an user entry.
By using two <sqf:add> elements, the context node gets both, the xml:lang and the lang attribute.
The Schematron QuickFix schema uses the following SQF elements:
Please read the reference in order to learn the functionality of these SQF elements.
From a formal point of view, the following XHTML instance is valid but from the point of view of a publisher, there are many logical errors.
1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <?xml-model href="quickFix2.sch" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?> |
3 | <html xmlns="http://www.w3.org/1999/xhtml"> |
4 | <head> |
5 | <title>Headlines hierarchy</title> |
6 | </head> |
7 | <body> |
8 | <h2>headline level 2</h2> |
9 | <h1>headline level 1</h1> |
10 | <p>paragraph</p> |
11 | <h4>headline level 4</h4> |
12 | <h1>headline level 1</h1> |
13 | <p>paragraph</p> |
14 | <h3>headline level 3</h3> |
15 | <h1>headline level 1</h1> |
16 | <h3>headline level 3</h3> |
17 | <h4>headline level 4</h4> |
18 | </body> |
19 | </html> |
Publishers are thinking in chapter hierarchies. So, the first headline should always have the highest hierarchy level. The headlines should also not have a hierarchical cleft.
The following Schematron schema identifies such clefts:
1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2" xmlns:es="http://www.escali.schematron-quickfix.com/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
3 | <es:default-namespace uri="http://www.w3.org/1999/xhtml"/> |
4 | <ns uri="http://www.w3.org/1999/xhtml" prefix="html"/> |
5 | <pattern> |
6 | <rule context="body"> |
7 | <let name="firstDescendantHeading" value="descendant::node()[name()=('h1','h2','h3','h4','h5','h6')][1]"/> |
8 | <assert test="$firstDescendantHeading/self::h1">The first headline of a HTML document has to be of level 1.</assert> |
9 | </rule> |
10 | <rule context="h3"> |
11 | <let name="firstPrecedingHeading" value="preceding::node()[name()=('h1','h2','h3','h4','h5','h6')][1]"/> |
12 | <report test="$firstPrecedingHeading/self::h1">A headline is missing on level 2: A <h3> should not follow a <<name path="$firstPrecedingHeading"/>>.</report> |
13 | </rule> |
14 | <rule context="h4"> |
15 | <let name="firstPrecedingHeading" value="preceding::node()[name()=('h1','h2','h3','h4','h5','h6')][1]"/> |
16 | <report test="$firstPrecedingHeading/self::h1">The headlines on level 2 and 3 are missing: A <h4> should not follow a <<name path="$firstPrecedingHeading"/>>.</report> |
17 | <report test="$firstPrecedingHeading/self::h2">The headline on level 3 is missing: A <h4> should not follow a <<name path="$firstPrecedingHeading"/>>.</report> |
18 | </rule> |
19 | </pattern> |
20 | </schema> |
5 | <rule context="body"> |
6 | <let name="firstDescendantHeading" value="descendant::node()[name()=('h1','h2','h3','h4','h5','h6')][1]"/> |
7 | <assert test="$firstDescendantHeading/self::h1" sqf:fix="setH1 add2bodyH1 addPrecH1 delete">The first headline of a HTML document has to be of level 1.</assert> |
8 | <sqf:fix id="setH1"> |
9 | <sqf:description> |
10 | <sqf:title>The first headline will be transformed into a <h1>.</sqf:title> |
11 | </sqf:description> |
12 | <sqf:replace match="$firstDescendantHeading" target="h1" node-type="element"> |
13 | <value-of select="."/> |
14 | </sqf:replace> |
15 | </sqf:fix> |
16 | <sqf:fix id="add2bodyH1"> |
17 | <sqf:description> |
18 | <sqf:title>The body gets a <h1> as first child.</sqf:title> |
19 | </sqf:description> |
20 | <sqf:user-entry name="h1" type="xs:string" default="/html/head/title"> |
21 | <sqf:description> |
22 | <sqf:title>Enter the text of the new headline.</sqf:title> |
23 | </sqf:description> |
24 | </sqf:user-entry> |
25 | <sqf:add position="first-child" target="h1" node-type="element"> |
26 | <value-of select="$h1"/> |
27 | </sqf:add> |
28 | </sqf:fix> |
29 | <sqf:fix id="addPrecH1"> |
30 | <sqf:description> |
31 | <sqf:title>A <h1> element will be inserted before the first headline.</sqf:title> |
32 | </sqf:description> |
33 | <sqf:user-entry name="h1" default="concat('[', /html/head/title ,']')"> |
34 | <sqf:description> |
35 | <sqf:title>Enter the text of the new headline.</sqf:title> |
36 | </sqf:description> |
37 | </sqf:user-entry> |
38 | <sqf:add match="$firstDescendantHeading" position="before" target="h1" node-type="element"> |
39 | <value-of select="$h1"/> |
40 | </sqf:add> |
41 | </sqf:fix> |
42 | <sqf:fix id="delete" use-when="($firstDescendantHeading/following::node()[matches(name(),'h[123456]')])[1]/self::h1"> |
43 | <sqf:description> |
44 | <sqf:title>Delete the first headline (<<name path="$firstDescendantHeading"/>>).</sqf:title> |
45 | </sqf:description> |
46 | <sqf:delete match="$firstDescendantHeading"/> |
47 | </sqf:fix> |
48 | </rule> |
49 | <rule context="h3"> |
50 | <let name="firstPrecedingHeading" value="preceding::node()[name()=('h1','h2','h3','h4','h5','h6')][1]"/> |
[...] | |
54 | <sqf:title>Delete the headline <h3>.</sqf:title> |
55 | </sqf:description> |
56 | <sqf:delete/> |
57 | </sqf:fix> |
58 | <sqf:fix id="setH1ToH2" use-when="$firstPrecedingHeading/preceding::node()[matches(name(),'h[123456]')]"> |
59 | <sqf:description> |
60 | <sqf:title>Replace the <h1> by a <h2>.</sqf:title> |
61 | </sqf:description> |
62 | <sqf:replace match="$firstPrecedingHeading" target="h2" node-type="element"> |
63 | <value-of select="."/> |
64 | </sqf:replace> |
65 | </sqf:fix> |
66 | <sqf:fix id="setH3ToH2" use-when="not((following::node()[matches(name(),'h[123456]')])[1]/self::h4)"> |
67 | <sqf:description> |
68 | <sqf:title>Replace the <h3> by a <h2>.</sqf:title> |
69 | </sqf:description> |
70 | <sqf:replace target="h2" node-type="element"> |
71 | <value-of select="."/> |
72 | </sqf:replace> |
73 | </sqf:fix> |
74 | <sqf:fix id="addH2beforeH3"> |
75 | <sqf:description> |
76 | <sqf:title>Add a <h2> before the <h3>.</sqf:title> |
77 | </sqf:description> |
78 | <sqf:user-entry name="h2" type="xs:string"> |
79 | <sqf:description> |
80 | <sqf:title>Enter the text of the new headline.</sqf:title> |
81 | </sqf:description> |
82 | </sqf:user-entry> |
83 | <sqf:add position="before" target="h2" node-type="element"> |
84 | <value-of select="$h2"/> |
85 | </sqf:add> |
86 | </sqf:fix> |
87 | <sqf:fix id="addH2afterH1"> |
88 | <sqf:description> |
89 | <sqf:title>Add a <h2> after the <h1>.</sqf:title> |
90 | </sqf:description> |
91 | <sqf:user-entry name="h2" type="xs:string"> |
92 | <sqf:description> |
93 | <sqf:title>Enter the text of the new headline.</sqf:title> |
94 | </sqf:description> |
95 | </sqf:user-entry> |
96 | <sqf:add match="$firstPrecedingHeading" position="after" target="h2" node-type="element"> |
97 | <value-of select="$h2"/> |
98 | </sqf:add> |
99 | </sqf:fix> |
100 | </rule> |
101 | <rule context="h4"> |
102 | <let name="firstPrecedingHeading" value="preceding::node()[name()=('h1','h2','h3','h4','h5','h6')][1]"/> |
103 | <report test="$firstPrecedingHeading/self::h1" sqf:fix="add2 delete setH4ToH2">The headlines on level 2 and 3 are missing: A <h4> should not follow a <<name path="$firstPrecedingHeading"/>>.</report> |
104 | <report test="$firstPrecedingHeading/self::h2" sqf:fix="add delete setH4ToH3">The headline on level 3 is missing: A <h4> should not follow a <<name path="$firstPrecedingHeading"/>>.</report> |
[...] | |
108 | </sqf:description> |
109 | <sqf:user-entry name="h3" type="xs:string"> |
110 | <sqf:description> |
111 | <sqf:title>Enter the text of the <h3>.</sqf:title> |
112 | </sqf:description> |
113 | </sqf:user-entry> |
114 | <sqf:add match="$firstPrecedingHeading" position="after" target="h3" node-type="element"> |
115 | <value-of select="$h3"/> |
116 | </sqf:add> |
117 | </sqf:fix> |
118 | <sqf:fix id="add2"> |
119 | <sqf:description> |
120 | <sqf:title>Add a <h2> and a <h3> before the <h4>.</sqf:title> |
121 | </sqf:description> |
122 | <sqf:user-entry name="h2" type="xs:string"> |
123 | <sqf:description> |
124 | <sqf:title>Enter the text of the <h2>.</sqf:title> |
125 | </sqf:description> |
126 | </sqf:user-entry> |
127 | <sqf:user-entry name="h3" type="xs:string"> |
128 | <sqf:description> |
129 | <sqf:title>Enter the text of the <h3>.</sqf:title> |
130 | </sqf:description> |
131 | </sqf:user-entry> |
132 | <sqf:add match="$firstPrecedingHeading" position="after" target="h3" node-type="element"> |
133 | <value-of select="$h3"/> |
134 | </sqf:add> |
135 | <sqf:add match="$firstPrecedingHeading" position="after" target="h2" node-type="element"> |
136 | <value-of select="$h2"/> |
137 | </sqf:add> |
138 | </sqf:fix> |
139 | <sqf:fix id="setH4ToH2" use-when="not((following::node()[matches(name(),'h[123456]')])[1]/(self::h4|self::h5))"> |
140 | <sqf:description> |
141 | <sqf:title>Replace the <h4> by a <h2>.</sqf:title> |
142 | </sqf:description> |
143 | <sqf:replace target="h2" node-type="element"> |
144 | <value-of select="."/> |
145 | </sqf:replace> |
146 | </sqf:fix> |
147 | <sqf:fix id="setH4ToH3" use-when="not((following::node()[matches(name(),'h[123456]')])[1]/self::h5)"> |
148 | <sqf:description> |
149 | <sqf:title>Replace the <h4> by a <h3>.</sqf:title> |
150 | </sqf:description> |
151 | <sqf:replace target="h3" node-type="element"> |
152 | <value-of select="."/> |
153 | </sqf:replace> |
154 | </sqf:fix> |
155 | <sqf:fix id="delete" use-when="not((following::node()[matches(name(),'h[123456]')])[1]/(self::h4|self::h5))"> |
156 | <sqf:description> |
157 | <sqf:title>Delete the headline <h4>.</sqf:title> |
158 | </sqf:description> |
159 | <sqf:delete/> |
160 | </sqf:fix> |
161 | </rule> |
162 | </pattern> |
163 | </schema> |
The schema has several QuickFixes. They can be ordered by the kind of their activity element:
Delete QuickFixes: They delete one of the misplaced headlines. They are only available if the order of the headlines would be correct after deleting the wrong headline.
Add QuickFixes: They insert missing headlines. The text content of the new headline is entered by the user (<sqf:user-entry>).
Replace QuickFixes: They change the levels of the misplaced headlines. They are only available if the order of the headlines would be correct after replacing the misplaced headlines.
The Schematron QuickFix schema uses the following SQF elements:
Please read the reference in order to learn the functionality of these SQF elements.
This example is made for SEO specialists. Google does not display HTML titles having a too long content. So, it is useful to check whether the HTML document has no titles with more than 70 characters.
In the instance the content of the <title> element is longer than 70 characters. In addition, there is a comment which is not allowed there.
1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <?xml-model href="quickFix3.sch" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?> |
3 | <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> |
4 | <head> |
5 | <title>No useful <title> element with too many characters because it contains more than 70 characters and a <!--forbidden comment-->.</title> |
6 | </head> |
7 | <body> |
8 | <h1>Useful and short title</h1> |
9 | <!-- Content --> |
10 | </body> |
11 | </html> |
The schema checks whether the title is short enough and includes no forbidden comment but text.
1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <schema xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:es="http://www.escali.schematron-quickfix.com/" queryBinding="xslt2"> |
3 | <es:default-namespace uri="http://www.w3.org/1999/xhtml"/> |
4 | <pattern> |
5 | <rule context="title"> |
6 | <let name="titleText" value="string-join(.//text(),'')"/> |
7 | <report test="comment()">Comments are forbidden in <title> elements.</report> |
8 | <report test="string-length($titleText) gt 70">The text in the <title> element is too long.</report> |
9 | <report test="normalize-space($titleText) = ''">There is no title!</report> |
10 | </rule> |
11 | </pattern> |
12 | </schema> |
6 | <rule context="title"> |
7 | <let name="titleText" value="string-join(.//text(),'')"/> |
8 | <report test="comment()" sqf:fix="deleteComment resolveComment" sqf:default-fix="deleteComment">Comments are forbidden in <title> elements.</report> |
9 | <report test="string-length($titleText) gt 70" sqf:fix="cutTitle setTitle takeComment newTitle">The text in the <title> element is too long.</report> |
10 | <report test="normalize-space($titleText) = ''" sqf:fix="setTitle newTitle">There is no title!</report> |
11 | <sqf:fix id="deleteComment"> |
12 | <sqf:description> |
13 | <sqf:title>Delete the comment.</sqf:title> |
14 | </sqf:description> |
15 | <sqf:delete match="comment()"/> |
16 | </sqf:fix> |
17 | <sqf:fix id="resolveComment" use-when="not(string-length($titleText) + string-length(string-join(.//comment(),'')) gt 70)"> |
18 | <sqf:description> |
19 | <sqf:title>Change the comment into text.</sqf:title> |
20 | </sqf:description> |
21 | <sqf:replace match="comment()"> |
22 | <value-of select="."/> |
23 | </sqf:replace> |
24 | </sqf:fix> |
25 | <sqf:fix id="cutTitle"> |
26 | <sqf:description> |
27 | <sqf:title>Cut the title after the 70th character.</sqf:title> |
28 | </sqf:description> |
29 | <sqf:replace target="title" node-type="element"> |
30 | <value-of select="substring(.,1,70)"/> |
31 | </sqf:replace> |
32 | </sqf:fix> |
33 | <sqf:fix id="setTitle" use-when="//h1"> |
34 | <sqf:description> |
35 | <sqf:title>Take the title from the first headline.</sqf:title> |
36 | </sqf:description> |
37 | <sqf:replace target="title" node-type="element"> |
38 | <value-of select="//h1[1]"/> |
39 | </sqf:replace> |
40 | </sqf:fix> |
41 | <sqf:fix id="newTitle"> |
42 | <sqf:description> |
43 | <sqf:title>Set a new title.</sqf:title> |
44 | </sqf:description> |
45 | <sqf:user-entry name="newText" type="xs:string"> |
46 | <sqf:description> |
47 | <sqf:title>Enter the text of the new title.</sqf:title> |
48 | </sqf:description> |
49 | </sqf:user-entry> |
50 | <sqf:replace target="title" node-type="element"> |
51 | <value-of select="$newText"/> |
52 | </sqf:replace> |
53 | </sqf:fix> |
54 | <sqf:fix id="takeComment" use-when="comment()"> |
55 | <sqf:description> |
56 | <sqf:title>Use the forbidden comment as new title.</sqf:title> |
57 | </sqf:description> |
58 | <sqf:replace target="title" node-type="element"> |
59 | <value-of select="comment()"/> |
60 | </sqf:replace> |
61 | </sqf:fix> |
62 | </rule> |
63 | </pattern> |
The schema provides two QuickFixes for forbidden comments, three for long titles and one for both.
The schema proposes to delete or to uncomment the forbidden comment.
Long titles could be cut after the 70th character, replaced by the first headline of the page or newly created by the user. In case a title is too long and contains a comment, a possible fix would be to take the new title from the comment.
The Schematron QuickFix schema uses the following SQF elements:
Please read the reference in order to learn the functionality of these SQF elements.
© Copyright 2014-2018 Nico Kutscherauer (last update 2018-07-17)
Imprint – Privacy Policy – Contact – Sitemap