nico
nico

Reputation: 1332

Schematron to check the location of given class attribute values

I’d like to create a Schematron schema that validates:

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

Answers (3)

Nico Kutscherauer
Nico Kutscherauer

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

Mads Hansen
Mads Hansen

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

Martin Honnen
Martin Honnen

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

Related Questions