How to group consecutive dates in XSLT?

巧了我就是萌 提交于 2020-01-06 04:45:06

问题


I have an xml file (sample below) and I want to group this xml based on consecutive Time_Off_Date.

<Root>
  <Entry>
    <Employee_ID>101</Employee_ID>
    <Time_Off_Details>
      <Time_Off_Date>2017-12-01</Time_Off_Date>
    </Time_Off_Details>
    <Time_Off_Details>
      <Time_Off_Date>2017-12-02</Time_Off_Date>
    </Time_Off_Details>
    <Time_Off_Details>
      <Time_Off_Date>2017-12-04</Time_Off_Date>
    </Time_Off_Details>
    <Time_Off_Details>
      <Time_Off_Date>2017-12-05</Time_Off_Date>
    </Time_Off_Details> 
  </Entry>
  <Entry>
    <Employee_ID>102</Employee_ID>
    <Time_Off_Details>
      <Time_Off_Date>2017-12-10</Time_Off_Date>
    </Time_Off_Details>
    <Time_Off_Details>
      <Time_Off_Date>2017-12-13</Time_Off_Date>
    </Time_Off_Details>
    <Time_Off_Details>
      <Time_Off_Date>2017-12-14</Time_Off_Date>
    </Time_Off_Details>
  </Entry>
</Root>

The final output should look like this (in CSV format).

Employee ID   Time Off Start  Time Off End
101           12/1/2017       12/2/2017
101           12/4/2017       12/5/2017
102           12/10/2017      12/10/2017
102           12/13/2017      12/14/2017

Is there a way to achieve this using XSLT 2.0 and without using recursive functions?? I am new to XSLT so any advice is appreciated.


回答1:


This can be nicely expressed in XQuery 3 using the tumbling window clause (https://www.w3.org/TR/xquery-31/#id-tumbling-windows):

for $entry in Root/Entry
for tumbling window $date in $entry//Time_Off_Date/xs:date(.)
start $s when true()
end $e next $n when $n - $e gt xs:dayTimeDuration('P1D')
return string-join(($entry/Employee_ID, $date[1], $date[last()]), '&#9;')

http://xqueryfiddle.liberty-development.net/6qM2e25

As XSLT 2 processors like Saxon 9 or XmlPrime also support XQuery this might be an alternative to using XSLT.

For XSLT you might need to explain why you don't want to use a recursive function.




回答2:


If the logic is that the input XML only holds individual days off and you want to group these individual days where they happen to be consecutive, then you can use xsl:for-each-group to select the Time_Off_Details with the group-starting-with set to the elements where the Time_Off_Date is not consecutive with the previous element.

Try this XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                version="2.0">

  <xsl:output method="text" />
  <xsl:strip-space elements="*" />

  <xsl:template match="Entry">
    <xsl:for-each-group select="Time_Off_Details" 
                        group-starting-with="*[not(xs:date(Time_Off_Date) = xs:date(preceding-sibling::*[1]/Time_Off_Date) + xs:dayTimeDuration('P1D'))]">
        <xsl:value-of select="../Employee_ID" />
        <xsl:text>,</xsl:text>
        <xsl:value-of select="Time_Off_Date" />
        <xsl:text>,</xsl:text>
        <xsl:value-of select="current-group()[last()]/Time_Off_Date" />
        <xsl:text>&#10;</xsl:text>
    </xsl:for-each-group>
  </xsl:template>
</xsl:stylesheet>



回答3:


Your task can be done in XSLT 2.0 using for-each-group.

First you have to sort all Time_Off_Date elements by their full content.

Each group starts with a Time_Off_Date element, for which it does not exist any other Time_Off_Date element with the content equal to the previous date, compared to the current date.

To compute the previous date, as a string, the following sequence is needed:

  • Take the current date.
  • Subtract the period of 1 day.
  • Format it as yyyy-mm-dd.

Then, for each group you need:

  • Read the date from the first group member.
  • Read the date from the last group member.
  • Print Employee_ID and both dates, formatted as you require.

So the whole script can look like below:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xsl:output method="text"/>

  <xsl:template match="Root">
    <xsl:text>Employee ID,Time Off Start,Time Off End&#xA;</xsl:text>
      <xsl:for-each-group select="Entry/Time_Off_Details/Time_Off_Date"
        group-starting-with=".[not(//Entry/Time_Off_Details/Time_Off_Date[. =
          format-date(xs:date(current()) - xs:dayTimeDuration('P1D'),
          '[Y0001]-[M01]-[D01]')])]">
        <xsl:sort select="."/>
        <xsl:variable name="startDate" select="current-group()[1]"/>
        <xsl:variable name="lastDate" select="current-group()[last()]"/>
        <xsl:value-of select="../../Employee_ID"/>
        <xsl:text>,</xsl:text>
        <xsl:value-of select="format-date($startDate,'[M01]/[D1]/[Y0001]')"/>
        <xsl:text>,</xsl:text>
        <xsl:value-of select="format-date($lastDate,'[M01]/[D1]/[Y0001]')"/>
        <xsl:text>&#xA;</xsl:text>
      </xsl:for-each-group>
  </xsl:template>
</xsl:transform>


来源:https://stackoverflow.com/questions/48737431/how-to-group-consecutive-dates-in-xslt

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!