Reputation: 1332
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>
Upvotes: 1
Views: 100
Reputation: 340
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>
Upvotes: 1
Reputation: 66714
The XPath for your context is not right. If you want it to evaluate for any/all elements //*
then the predicate of self::html/body/main/div/div
would only match on the root node and none of the elements.
If you are trying to assert that the matched element is not /html/body/main/div/div
then you could try:
<rule context="//*[not(generate-id(.) = /html/body/main/div/div/generate-id())]">
Upvotes: 0
Reputation: 167401
I would try
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt3">
<ns prefix="xhtml" uri="http://www.w3.org/1999/xhtml"/>
<let name="cats" value="('cat1', 'cat2')"/>
<let name="divs" value="/xhtml:html/xhtml:body/xhtml:main/xhtml:div/xhtml:div"/>
<pattern>
<rule context="*[not(. intersect $divs)]/@class">
<assert test="every $cat in $cats satisfies not(contains-token(., $cat))">@class '<value-of select="."/>' contains one of $cats values '<value-of select="$cats"/>'</assert>
</rule>
<rule context="$divs">
<assert test="@class = $cats">@class value '<value-of select="@class"/>' not equal to $cats '<value-of select="$cats"/>'</assert>
</rule>
</pattern>
</schema>
I have assumed xslt3 binding as these days Schematron is usually compiled with a current version of Saxon and these support XSLT 3.
Most of it should work as xslt2 as well, not sure about contains-token.
Upvotes: 1