Search code examples
xmlxsltxpathxslt-2.0schematron

Schematron - Element validation based on its position


I am using Schematron to do some business rule validations. My xml data looks like:

<labtests>  
    <test>
        <observation>
            <code code="TT900" name="NMCK"/>
            <outcome value="074042"/>
        </observation>            
    </test>

    <test>
        <observation>
            <code code="TT500" name="LVCT"/>
            <outcome value="852417"/>
        </observation>            
    </test>
    <test>
        <observation>
            <code code="TT500" name="LVCT"/>
            <outcome value="36542"/>
        </observation>            
    </test>
    <test>
        <observation>
            <code code="TT100" name="GVMC"/>
            <outcome value="874541"/>
        </observation>            
    </test>
    <test>
        <observation>
            <code code="TT500" name="LVCT"/>
            <outcome value="369521"/>
        </observation>            
    </test>
</labtests>

The current context is set to labtests/test/observation like below:

<iso:rule context="labtests/test/observation">
    <!--perform all validations here-->


</iso:rule>               

I want to perform some special business validation checks on the <outcome> node for the first <observation> block having code/@code="TT500".

I think I can use the following expression to get the position of first intended <observation> block

count(../../test/observation/code[@code="TT500"]/preceding-sibling::*)+1

but I don't know how to compare this position with the node in the current context to perform special validation.

UPDATE:

For the sake of simplicity let's assume that the special validation to be performed in this case is that the length of outcome/@value must be greater than or equal to 6. i.e.

<iso:report test="not(string-length(outcome/@value) >= 6">
    outcome/@value should have at least 6 characters for the first TT500 observation
</iso:report>

Solution

  • The following Schematron document does exactly what you have asked. There is no real difference between assert and report, you can invert any rule to fit both.

    <?xml version="1.0" encoding="UTF-8"?>
    <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
        <pattern>
            <rule context="observation[code/@code = 'TT500' and not(preceding::observation[code/@code = 'TT500'])]">
                <assert test="string-length(outcome/@value) ge 6"> outcome/@value should have at least 6 characters for the first TT500 observation </assert>
            </rule>
        </pattern>
    </schema>
    

    When the following (invalid) XML document is validated with this SCH rule:

    <?xml version="1.0" encoding="UTF-8"?>
    <?xml-model href="sample.sch" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
    <labtests>  
        <test>
            <observation>
                <code code="TT900" name="NMCK"/>
                <outcome value="07442"/>
            </observation>            
        </test>
        <test>
            <observation>
                <code code="TT500" name="LVCT"/>
                <outcome value="85417"/>
            </observation>            
        </test>
        <test>
            <observation>
                <code code="TT500" name="LVCT"/>
                <outcome value="36542"/>
            </observation>            
        </test>           
    </labtests>
    

    A Schematron processor will issue a warning along the lines of

    E [ISO Schematron] outcome/@value should have at least 6 characters for the first TT500 observation