【问题标题】:使用 group-starting-with 对特定模式的记录进行分组
【发布时间】:2022-01-17 21:07:26
【问题描述】:

我对分组员工休假有一个非常具体的要求,其中天是连续的,持续时间是半天。这是为了将它们合并为一个完整的值,其中请假在夜班工作。复杂性来自一个事实,即不能连续超过 2 天被合并到一个组中。

示例 XML

<wd:Report_Data xmlns:wd="urn:com.workday.report/WFM_Future_Leave_Report">
    <wd:Report_Entry>
        <wd:Worker_group>
            <wd:Partner_ID>11111111</wd:Partner_ID>
            <wd:unit_id>001</wd:unit_id>
        </wd:Worker_group>
        <wd:Partner_ID>80710042</wd:Partner_ID>
        <wd:Time_Off_Details_group>
            <wd:Effective_Date>2021-09-06-07:00</wd:Effective_Date>
            <wd:Eligible>1</wd:Eligible>
            <wd:Duration>0.5</wd:Duration>
            <wd:Status wd:Descriptor="A">
            </wd:Status>
        </wd:Time_Off_Details_group>
        <wd:Time_Off_Details_group>
            <wd:Effective_Date>2021-09-07-07:00</wd:Effective_Date>
            <wd:Eligible>1</wd:Eligible>
            <wd:Duration>0.5</wd:Duration>
            <wd:Status wd:Descriptor="A">
            </wd:Status>
        </wd:Time_Off_Details_group>
        <wd:Time_Off_Details_group>
            <wd:Effective_Date>2021-09-08-07:00</wd:Effective_Date>
            <wd:Eligible>1</wd:Eligible>
            <wd:Duration>0.5</wd:Duration>
            <wd:Status wd:Descriptor="A">
            </wd:Status>
        </wd:Time_Off_Details_group>
        <wd:Time_Off_Details_group>
            <wd:Effective_Date>2021-09-09-07:00</wd:Effective_Date>
            <wd:Eligible>0</wd:Eligible>
            <wd:Duration>1</wd:Duration>
            <wd:Status wd:Descriptor="A">
            </wd:Status>
        </wd:Time_Off_Details_group>
        <wd:Time_Off_Details_group>
            <wd:Effective_Date>2021-09-10-07:00</wd:Effective_Date>
            <wd:Eligible>1</wd:Eligible>
            <wd:Duration>0.5</wd:Duration>
            <wd:Status wd:Descriptor="A">
            </wd:Status>
        </wd:Time_Off_Details_group>
        <wd:Time_Off_Details_group>
            <wd:Effective_Date>2021-09-11-07:00</wd:Effective_Date>
            <wd:Eligible>1</wd:Eligible>
            <wd:Duration>0.5</wd:Duration>
            <wd:Status wd:Descriptor="A">
            </wd:Status>
        </wd:Time_Off_Details_group>
        <wd:Time_Off_Details_group>
            <wd:Effective_Date>2021-09-12-07:00</wd:Effective_Date>
            <wd:Eligible>1</wd:Eligible>
            <wd:Duration>0.5</wd:Duration>
            <wd:Status wd:Descriptor="A">
            </wd:Status>
        </wd:Time_Off_Details_group>
        <wd:Time_Off_Details_group>
            <wd:Effective_Date>2021-09-13-07:00</wd:Effective_Date>
            <wd:Eligible>1</wd:Eligible>
            <wd:Duration>0.5</wd:Duration>
            <wd:Status wd:Descriptor="A">
            </wd:Status>
        </wd:Time_Off_Details_group>
    </wd:Report_Entry>
</wd:Report_Data>

而我一直在使用的 XSLT,我知道这会将有效日期中断的叶子分组,所以根本不会做我想要的。

    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0"
    xmlns:wd="urn:com.workday.report/WFM_Future_Leave_Report"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xtt="urn:com.workday/xtt">
    
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*" />

        
    <xsl:template match="wd:Report_Entry">
            <xsl:for-each-group select="wd:Time_Off_Details_group[wd:Eligible = '1' and wd:Status/@wd:Descriptor = 'A']"       
            group-starting-with="*[not(xs:date(wd:Effective_Date) = xs:date(preceding-sibling::*[1]/wd:Effective_Date) + xs:dayTimeDuration('P1D'))]">      
                <xsl:value-of select="../wd:Partner_ID" />
                <xsl:text>,</xsl:text>
                <xsl:value-of select="../wd:Worker_group/wd:unit_id"/>  
                <xsl:text>,</xsl:text>
                <xsl:value-of select="current-group()[1]/format-date(wd:Effective_Date,'[D1]/[M01]/[Y0001]')" />
                <xsl:text>,</xsl:text>
                <xsl:value-of select="sum(current-group()/wd:Duration)"/>
                <xsl:text>,</xsl:text>
                <xsl:value-of select="wd:Status/@wd:Descriptor"/>
                <xsl:text>,</xsl:text>
                <xsl:text>NIGHTSHIFTLOGIC</xsl:text>
                <xsl:text>&#xA;</xsl:text>
            </xsl:for-each-group>
        
        </xsl:template>
    <xsl:template name="match_text" match="text()"/>

</xsl:stylesheet>

(Eligible = ‘1’ 标记持续时间为 0.5,Status = ‘A’ 仅过滤出已批准的休假)

在这个例子中,期望的输出是:

11111111,001,06/09/2021,1,A
11111111,001,08/09/2021,0.5,A
11111111,001,09/09/2021,1,A
11111111,001,10/09/2021,1,A
11111111,001,12/09/2021,1,A

这是因为 06/09 和 07/09 的 0.5 个持续时间合并为一整天。第一组不应考虑下一个连续日,因为它没有后续 0.5 天的持续时间实例,应独立输出。同样,如果 09/09 存在一整天的持续时间,则需要按原样输出。分组在 10/09 的下一个 0.5 持续时间条目再次开始,并与 11/09 相结合,后者输出另一个全天,然后再次为接下来的两个连续有效日期条目。

【问题讨论】:

  • 一个示例 XML 文件将使其更容易推理,并能够提出解决方案。如果它们是连续的,那么将日期按两个分组的唯一标准是?例如,如果日期是 1/12、3/12(不是 2 月),您会希望它们被分组吗?
  • 您使用的是哪个处理器,哪个工具?使用 XQuery 的翻转窗口和各种变量,条件可能比使用 XSLT 2/3 的 group-starting-with 更容易表达。当然,如果您展示您的 XML 输入结构以及您在 XSLT 中使用 group-starting-with 进行的尝试,这将有所帮助。

标签: xslt


【解决方案1】:

SaxonCS 中的 XSLT 4 与 break-when 上的 for-each-group 似乎可以做到这一点:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="4.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  xpath-default-namespace="urn:com.workday.report/WFM_Future_Leave_Report"
  xmlns:wd="urn:com.workday.report/WFM_Future_Leave_Report"
  xmlns:mf="http://example.com/mf"
  expand-text="yes">

  <xsl:output method="text"/>
  
  <xsl:mode on-no-match="shallow-skip"/>
  
  <xsl:function name="mf:extract-date" as="xs:date">
    <xsl:param name="date" as="element(Effective_Date)"/>
    <xsl:sequence select="$date => substring(1, 10) => xs:date()"/>
  </xsl:function>

  <xsl:template match="Report_Entry">
    <xsl:for-each-group select="Time_Off_Details_group" break-when="sum($group/Duration) = 1 and count($group) = (1, 2) or sum(($group, $next)!Duration) gt 1">
      <xsl:value-of
        select="..!Worker_group!(Partner_ID, unit_id), 
                Effective_Date => mf:extract-date() => format-date('[D01]/[M01]/[Y0001]'), 
                sum(current-group()/Duration), 
                Status/@wd:Descriptor"
        separator=","/>
        <xsl:text>&#10;</xsl:text>
    </xsl:for-each-group>
  </xsl:template>

</xsl:stylesheet>

虽然到目前为止我使用的条件只是检查sum 和每组中的项目数(例如不超过 2 个),但它并没有查看日期,因为至少在示例输入中有以 1 天递增的连续日期序列。

或者,如果你使用 Saxon,你也可以使用 XQuery 和 tumbling window 子句

declare namespace wd = "urn:com.workday.report/WFM_Future_Leave_Report";

declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";

declare option output:method 'text';
declare option output:item-separator '&#10;';

for $re in wd:Report_Data/wd:Report_Entry
for tumbling window $days in $re/wd:Time_Off_Details_group
start $d1 at $sp when true()
only end $d2 at $ep next $nd when 
  (xs:date($d2/wd:Effective_Date/substring(., 1, 10)) - xs:dayTimeDuration('P1D'))
  = xs:date($d1/wd:Effective_Date/substring(., 1, 10)) and $ep - 1 = $sp
  and sum(($d1, $d2)!wd:Duration) = 1
  or $d1/wd:Duration = 0.5 and $nd/wd:Duration > 0.5
  or $d1/wd:Duration = 1
  
return string-join(
    (
      $days[1]/../wd:Worker_group/(wd:Partner_ID, wd:unit_id),
      $days[1]/wd:Effective_Date/substring(., 1, 10),
      sum($days/wd:Duration)
    ),
    ','
  )

【讨论】:

  • 谢谢。我正在使用 SaxonEE,在尝试收到的第一个示例时:必须指定属性 group-by、group-adjacent、group-starting-with 和 group-ending-with 中的一个我已经添加了属性和值合并为 4.5。
  • 在 Java 世界以及 Saxon EE 和 XSLT 中,我认为您需要等待 Saxon 11 的发布才能运行该代码。另一方面,XQuery 代码使用 Saxon 10 或 9.9 运行。
【解决方案2】:

我建议为此使用xsl:iterate,而不是xsl:for-each-group。在决定将下一项添加到现有组或开始新组时,它使您可以更灵活地决定要应用的条件。使用迭代参数来累积当前组的内容,然后当您决定开始一个新组时,处理该参数的值并将其重置为空序列以进行下一次迭代。

【讨论】:

  • 谢谢你,我会阅读,但我对迭代指令并不熟悉,任何建议或通用示例将不胜感激! J
猜你喜欢
  • 2015-03-17
  • 2022-01-12
  • 2015-08-26
  • 1970-01-01
  • 1970-01-01
  • 2012-07-17
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
相关资源
最近更新 更多