我有一个 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的新手,所以任何建议都值得赞赏。
如果逻辑是输入 XML 只保留个别休息日,并且您希望将这些恰好连续的单独日子分组,则可以使用xsl:for-each-group
选择group-starting-with
设置为Time_Off_Date
与前一个元素不连续的元素的Time_Off_Details
。
试试这个 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> </xsl:text>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
这可以在 XQuery 3 中使用翻转窗口子句 (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()]), '	')
http://xqueryfiddle.liberty-development.net/6qM2e25
由于 XSLT 2 处理器(如 Saxon 9 或 XmlPrime)也支持 XQuery,因此这可能是使用 XSLT 的替代方案。
对于 XSLT,您可能需要解释为什么不想使用递归函数。
可以使用for-each-group
在XSLT 2.0中完成您的任务。
首先,您必须按其完整内容对所有Time_Off_Date
元素进行排序。
每个组都以一个Time_Off_Date
元素开头,而它没有Time_Off_Date
存在内容等于 上一个日期,与当前日期相比。
若要以字符串形式计算上一个日期,需要以下序列:
- 以当前日期为例。
- 减去 1 天的周期。
- 将其格式化为
yyyy-mm-dd
。
然后,对于每个组,您需要:
- 读取第一个小组成员的日期。
- 读取最后一个小组成员的日期。
- 打印Employee_ID和两个日期,并根据需要设置格式。
所以整个脚本可以如下所示:
<?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
</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>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
</xsl:transform>