1
votes

Imagine I have a simple xml file:

<intervals>
  <range from="2001-12-17T09:30:47Z" to="2001-12-19T11:35:16Z" />
  <range from="2002-12-17T09:30:47Z" to="2002-12-19T11:35:16Z" />
<intervals>

I would like to ensure that the date ranges given do not overlap. I can do this in code, obviously, but ideally I would like to provide a validation package to go with my xml schema to validate some business rules.

Can I use a schematron to validate that the ranges do not overlap?

1

1 Answers

1
votes

ISO Schematron is the tool for your problem. Just use the implementation for XPath 2.0 that allows data typing. Then you only have to test an assertion. Something like <assert test="@from &lt; @to"/>

See : http://www.schematron.com/implementation.html

Concerning overlap : you can code an XSLT function that return a boolean in your schematron schema and call it from your test attribute.

Schematron with XSLT function embedded :

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fct="localFunctions" queryBinding="xslt2"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" >
    <ns prefix="fct" uri="localFunctions"/>
    <pattern>
        <rule context="intervals">
            <assert test="fct:checkOverlaping((),range)" >OVERLAPING</assert>
        </rule>
    </pattern>
    <xsl:function name="fct:checkOverlaping" as="xs:boolean">
        <xsl:param name="currentEndDate" as="xs:dateTime?"/>
        <xsl:param name="ranges" as="node()*"/>
        <xsl:choose>
            <xsl:when test="not(exists($currentEndDate))">
                <xsl:variable name="orderedRanges" as="node()*">
                    <xsl:for-each select="$ranges">
                        <xsl:sort select="@from"/>
                        <xsl:copy-of select="."/>
                    </xsl:for-each>
                </xsl:variable>
                <xsl:value-of select="fct:checkOverlaping(xs:dateTime($orderedRanges[position()=1]/@to),$orderedRanges[position()>1])"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="
                    if (not(exists($ranges))) then true() else 
                    if ($currentEndDate > $ranges[position()=1]/@from) then false() else 
                    fct:checkOverlaping($ranges[position()=1]/@to,$ranges[position()>1])
                    "/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
</schema>

Use the following implementation : http://www.schematron.com/tmp/iso-schematron-xslt2.zip which implements allow-foreign and uses XPath 2.0. When this parameter allow-foreign is set to true, the implementation will embed the function above.

How the function works : first loop (currentEndDate not set) the range elements are sorted by from date. Other recursive loop (after sorting) : test if the to date of the first range is before the from date of the second date and go on for the next if it is true (otherwise false and stop).