如何在XSLT中对连续日期进行分组?

时间:2018-02-11 23:05:33

标签: xml xslt xslt-1.0 xslt-2.0 xslt-grouping

我有一个xml文件(下面的示例),我想根据连续的Time_Off_Date对这个xml进行分组。

<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>

最终输出应如下所示(CSV格式)。

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

有没有办法使用XSLT 2.0实现这一点,而不使用递归函数? 我是XSLT的新手,所以任何建议都表示赞赏。

3 个答案:

答案 0 :(得分:1)

这可以使用翻滚窗口子句(https://www.w3.org/TR/xquery-31/#id-tumbling-windows)在XQuery 3中很好地表达:

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

由于像Saxon 9或XmlPrime这样的XSLT 2处理器也支持XQuery,因此这可能是使用XSLT的替代方案。

对于XSLT,您可能需要解释为什么不想使用递归函数。

答案 1 :(得分:1)

如果逻辑是输入XML仅关闭个别假日,并且您希望将这些日期分组连续,那么您可以使用xsl:for-each-group选择Time_Off_Details group-starting-with设置为Time_Off_Date与前一个元素不连续的元素。

试试这个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>

答案 2 :(得分:0)

您的任务可以使用for-each-group XSLT 2.0 中完成。

首先,您必须按其完整内容对所有Time_Off_Date元素进行排序。

每个组都以Time_Off_Date元素开头,而不是Time_Off_Date元素 存在任何其他yyyy-mm-dd元素,其内容等于 上一个日期,与当前日期相比。

要计算上一个日期,作为字符串,需要以下序列:

  • 取当前日期。
  • 减去1天的时间。
  • 将其格式化为<?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>

然后,对于您需要的每个小组:

  • 从第一个小组成员中读取日期。
  • 阅读上一个小组成员的日期。
  • 打印 Employee_ID 以及两个日期,根据需要进行格式化。

所以整个脚本可能如下所示:

--expose