Search code examples
xmlxpathxpath-3.0

XPath 3.x - sort function


I need to sort a sequence of elements using plain XPath 3.0, so no XQuery, no XSL-T and no code. I tried following this answer on how to use the sort function, but I'm not an expert in XPath, so I couldn't figure out how to use it.

So, my document basically has the following structure:

<?xml version="1.0" encoding="UTF-8"?>
<AppointmentList>
    <Appointment id="11" creatorID="1" creationDate="2018-03-01" reschedulable="ja" appointmentSeries="ja">
        <Start date="2018-03-14" time="12:00:00"/>
        <End date="2018-03-14" time="13:30:00"/>
        <Description>Vorführung</Description>
    </Appointment>

    <Appointment id="22" creatorID="2" creationDate="2018-02-14" reschedulable="ja" appointmentSeries="nein">
        <Start date="2018-03-20" time="13:00:00"/>
        <End date="2018-03-20" time="14:00:00"/>        
        <Description>Programm Meeting</Description>
        <Benachrichtigung art="EMail"/>
    </Appointment>

    <Appointment id="33" creatorID="3" creationDate="2018-02-23" reschedulable="nein" appointmentSeries="ja">
        <Start date="2018-02-24" time="15:00:00"/>
        <End date="2018-02-24" time="16:00:00"/>        
        <Description>Burglary Report</Description>
        <Benachrichtigung art="Beep"/>
    </Appointment>

    <Appointment id="44" creatorID="1" creationDate="2018-01-01" reschedulable="nein" appointmentSeries="nein">
        <Start date="2018-05-01" time="10:00:00"/>
        <End date="2018-05-01" time="17:00:00"/>        
        <Description>Besprechung Ministerium</Description>
    </Appointment>

    <Appointment id="55" creatorID="8" creationDate="2018-02-28" reschedulable="nein" appointmentSeries="nein">
        <Start date="2018-06-08" time="08:00:00"/>
        <End date="2018-06-09" time="18:00:00"/>        
        <Description>Spam Konferenz</Description>
    </Appointment>

    <Appointment id="66" creatorID="10" creationDate="2018-03-22" reschedulable="nein" appointmentSeries="nein">
        <Start date="2018-05-07" time="08:00:00"/>
        <End date="2018-05-07" time="18:00:00"/>        
        <Description>XML Tutorium</Description>
    </Appointment>

    <Appointment id="77" creatorID="9" creationDate="2018-03-15" reschedulable="ja" appointmentSeries="nein">
        <Start date="2018-04-20" time="08:00:00"/>
        <End date="2018-04-20" time="09:00:00"/>        
        <Description>Abschlussprüfung</Description>
    </Appointment>

    <Appointment id="88" creatorID="7" creationDate="2018-03-09" reschedulable="nein" appointmentSeries="ja">
        <Start date="2018-03-14" time="17:00:00"/>
        <End date="2018-03-14" time="18:00:00"/>        
        <Description>Versammlung Workaholics</Description>
    </Appointment>

    <Appointment id="99" creatorID="6" creationDate="2018-02-01" reschedulable="nein" appointmentSeries="nein">
        <Start date="2018-02-28" time="10:00:00"/>
        <End date="2018-02-28" time="17:00:00"/>        
        <Description>Fortbildung Datenbanken</Description>
    </Appointment>
</AppointmentList>

I want to get the three next appointments sorted by their date in ascending order. So the next appointment from now is in the first position of the result sequence and the appointment which starts on the 3rd closest date to now should be the sequence's last element. The query shown below gives me the appointments that did not already take place, but I can't figure out how to apply the sort function correctly.

/AppointmentList/Appointment[fn:dateTime(Start/@date, Start/@time) > fn:current-dateTime()]

Thanks, as I haven't been able to find any good documentation on the fn:sort function.


Solution

  • The function fn:sort(...) exists in three versions, with up to three arguments:

    1. $input as item()*: the input sequence,
    2. $collation as xs:string?: the collation for string comparison (or an empty sequence),
    3. $key as function(item()) as xs:anyAtomicType*) as item()*: a function item that extracts the sort key (i.e. the part that should be compared) from the items to sort.

    Since you don't want to sort strings, the second argument should be ().

    The sort key in your case is the starting date and time of each appointment. So you need to supply a function item that, given an Appointment node, returns the xs:dateTime of its start date: function($app) { fn:dateTime($app/Start/@date, $app/Start/@time) }

    Putting everything together:

    subsequence(
      sort(
        /AppointmentList/Appointment[fn:dateTime(Start/@date, Start/@time) > fn:current-dateTime()],
        (),
        function($app) { fn:dateTime($app/Start/@date, $app/Start/@time) }
      ),
      1,
      3
    )
    

    Result:

    <Appointment id="77" creatorID="9" creationDate="2018-03-15" reschedulable="ja" appointmentSeries="nein">
      <Start date="2018-04-20" time="08:00:00"/>
      <End date="2018-04-20" time="09:00:00"/>
      <Description>Abschlussprüfung</Description>
    </Appointment>
    <Appointment id="44" creatorID="1" creationDate="2018-01-01" reschedulable="nein" appointmentSeries="nein">
      <Start date="2018-05-01" time="10:00:00"/>
      <End date="2018-05-01" time="17:00:00"/>
      <Description>Besprechung Ministerium</Description>
    </Appointment>
    <Appointment id="66" creatorID="10" creationDate="2018-03-22" reschedulable="nein" appointmentSeries="nein">
      <Start date="2018-05-07" time="08:00:00"/>
      <End date="2018-05-07" time="18:00:00"/>
      <Description>XML Tutorium</Description>
    </Appointment>