I’d like to create a Schematron schema that validates:
/html/body/main/div/div
have an optional class
attribute, it contains one of the cat1
or cat2
values in any case./html/body/main/div/div
have an optional class
attribute, it does not in any case contain one of the values cat1
or cat2
.Here’s a valid example:
<html xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
<main>
<div class="name">
<div class="cat1 cat3"> <!— Other values are acceptable as long as cat1 or cat2 is present. —>
<div></div>
</div>
<div class="cat2">
<div></div>
</div>
<div> <!— No class attribute is OK —>
<div></div>
</div>
</div>
</main>
</body>
</html>
And an invalid example:
<html xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
<main>
<div class="name cat1"> <!— cat1 not allowed here —>
<div class="cat3"> <!— cat1 or cat2 missing —>
<div></div>
</div>
<div class="cat2">
<div class="cat2"></div> <!— cat2 not allowed here —>
</div>
<div class=""> <!— Empty class attribute is not OK —>
</div>
</div>
</main>
</body>
</html>
Here’s what I’ve got so far. The XPath query //*[not(self::html/body/main/div/div)]
doesn’t work. It also returns nodes at /html/body/main/div/div
.
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
<pattern id="test">
<rule context="//*[not(self::html/body/main/div/div)]">
<assert test="
not(contains(@class, ’cat1’)) and
not(contains(@class, ’cat2’))
" role="error">
The class cannot contain
"<value-of select="@class"/>".
</assert>
</rule>
</pattern>
</schema>
Martins Answer works (except of a small mistake) but it is unnecessarily complex in my opinion. If you want it simpler or needs to use XSLT2, you can do it in this way:
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
<ns prefix="xhtml" uri="http://www.w3.org/1999/xhtml"/>
<let name="cats" value="('cat1', 'cat2')"/>
<pattern>
<rule context="/xhtml:html/xhtml:body/xhtml:main/xhtml:div/xhtml:div[@class]">
<assert test="tokenize(@class, '\s') = $cats">Missing class <value-of select="string-join($cats, ' or ')"/>.</assert>
</rule>
<!--this rule will only checks elements which are not checked by the rule above-->
<rule context="*[@class]">
<let name="class" value="@class"/>
<!-- Just use report instead of assert to get the opposite check result. -->
<report test="tokenize($class, '\s') = $cats"
>Used forbidden class: <value-of select="$cats[. = tokenize($class, '\s')]"/>.</report>
</rule>
</pattern>
</schema>