XSLT 2.0:根据 for-each -group 的当前索引评估和修改元素值

Posted

技术标签:

【中文标题】XSLT 2.0:根据 for-each -group 的当前索引评估和修改元素值【英文标题】:XSLT 2.0: Evaluate and modify element value according to current index of for-each -group 【发布时间】:2021-11-06 22:28:15 【问题描述】:

我正在尝试将 EndDate 评估为下一个“StartDate -1”(下一个开始日期的前一天)。下面给出的是输入 XML:

<?xml version="1.0" encoding="UTF-8"?>
<queryCompoundEmployeeResponse>
   <CompoundEmployee>
         <id>176</id>
         <person>
               <action>NO CHANGE</action>
               <person_id_external>10005005</person_id_external>
               <personal_information>
                     <end_date>9999-12-31</end_date>
                     <start_date>2021-06-03</start_date>
               </personal_information>
               <personal_information>
                     <end_date>2021-06-02</end_date>
                     <start_date>2017-12-06</start_date>
               </personal_information>
               <phone_information>
                     <phone_type>B</phone_type>
               </phone_information>
               <phone_information>
                     <phone_number>7CAED430A494B3C404</phone_number>
               </phone_information>
               <email_information>
                     <last_modified_on>2019-03-25T02:44:51.000Z</last_modified_on>
               </email_information>
               <employment_information>
                     <start_date>2017-12-06</start_date>
                     <user_id>10005005</user_id>
                     <job_information>
                           <end_date>9999-12-31</end_date>
                           <start_date>2019-03-02</start_date>
                        </job_information>
               </employment_information>
         </person>
         <execution_timestamp>2021-07-14T15:08:38.000Z</execution_timestamp>
         <version_id>2105P0</version_id>
      <Start_Dates>
         <StartDate>2017-12-06</StartDate>
         <StartDate>2017-12-06</StartDate>
         <StartDate>2019-03-02</StartDate>
         <StartDate>2021-06-03</StartDate>
      </Start_Dates>
      <End_Dates>
         <EndDate>2021-06-02</EndDate>
         <EndDate>NA</EndDate>
         <EndDate>9999-12-31</EndDate>
         <EndDate>9999-12-31</EndDate>
      </End_Dates>
   </CompoundEmployee>
   </queryCompoundEmployeeResponse>

电流输出:

    <?xml version="1.0" encoding="UTF-8"?>
<queryCompoundEmployeeResponse xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <CompoundEmployee>
      <Person>
         <StartDate>2017-12-06</StartDate>
         <EndDate>2019-03-01</EndDate>
      </Person>
      <Person>
         <StartDate>2019-03-02</StartDate>
         <EndDate/>
      </Person>
      <Person>
         <StartDate>2021-06-03</StartDate>
         <EndDate>9999-12-31</EndDate>
      </Person>
   </CompoundEmployee>
</queryCompoundEmployeeResponse>

所需输出:

<?xml version="1.0" encoding="UTF-8"?>
<queryCompoundEmployeeResponse xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <CompoundEmployee>
      <Person>
         <StartDate>2017-12-06</StartDate>
         <EndDate>2019-03-01</EndDate>
      </Person>
      <Person>
         <StartDate>2019-03-02</StartDate>
         <EndDate>2021-06-02</EndDate>
      </Person>
      <Person>
         <StartDate>2021-06-03</StartDate>
         <EndDate>9999-12-31</EndDate>
      </Person>
   </CompoundEmployee>
</queryCompoundEmployeeResponse>

我正在尝试将每个 Person 节点的 EndDate 计算为一天减去下一个 StartDate。对于 last Person,EndDate 应该是输入 XML 中的最后一个 EndDate。

这是我要增强的代码:

<xsl:template match="/queryCompoundEmployeeResponse">
    <queryCompoundEmployeeResponse>
        <xsl:for-each select="CompoundEmployee">
            <xsl:copy>
                <xsl:variable name="person" select="person" />
                <xsl:for-each-group select="Start_Dates/StartDate" group-by=".">
                    <Person>
                       <xsl:copy-of select="."/>
                       
                        <!--Start of EndDate logic for last record-->
                        <xsl:variable name="nxtStartDate" select="following-sibling::StartDate"/>
                           <xsl:if test="not($nxtStartDate)">
                             <xsl:variable name="i" select="position()"/>
                                <EndDate>
                                    <xsl:value-of select="../following-sibling::End_Dates/EndDate[last()]"/>   
                                </EndDate>
                           </xsl:if>
                          <!-- End of EndDate logic for last record-->  
                          
                          <!--Calculate next start date -1 -->
                            <xsl:if test="$nxtStartDate">
                            <xsl:variable name="currentDate" select="Start_Dates/StartDate"/>
                            <xsl:variable name="i" select="position()"/>
                            <EndDate>
                                <xsl:apply-templates select="following-sibling::StartDate[$i+1]"/>
                            </EndDate>
                            </xsl:if>
                      <!--Calculate next start date -1 -->
                    
                     <!-- //Some additional required code://
                     
                       <xsl:copy-of select="$person/* except $person/(personal_information | phone_information | email_information | employment_information)"/>
                       <xsl:copy-of select="$person/personal_information[start_date le current() and current() le end_date]"/>
                       <xsl:copy-of select="$person/employment_information[start_date le current() and current() le end_date]"/>
                        <xsl:copy-of select="$person/employment_information/job_information[start_date le current() and current() le end_date]"/> 
                     -->
                    </Person>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:for-each>
    </queryCompoundEmployeeResponse>
</xsl:template>

<xsl:template match="StartDate">
 <xsl:variable name="sDate" select="." as="xs:date"/>
        <xsl:copy-of select="$sDate - 1*xs:dayTimeDuration('P1D')"/>
</xsl:template>
    
</xsl:stylesheet>



 I need something like following-sibling::(.)[index+1]. It gives me the End date in first Person. But next one is still empty. 

我做错了什么? 另外,如何修改它以使每个部分都有单独的模板?

提前致谢!

【问题讨论】:

【参考方案1】:

当您执行xsl:for-each-group 时,您将被置于组中第一项的上下文中。在此上下文中,您的表达式following-sibling::StartDate 选择原始 XML 文档中组的第一个 StartDate 的以下同级 - 不在当前组中。而且您当然不能使用任何轴来到达下一组的第一项 - 这是您真正想要的。

不妨这样试试:

XSLT 2.0

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/queryCompoundEmployeeResponse">
    <queryCompoundEmployeeResponse>
        <xsl:for-each select="CompoundEmployee">
            <xsl:copy>
            
                <xsl:variable name="start-dates">
                    <xsl:for-each-group select="Start_Dates/StartDate" group-by=".">
                        <xsl:copy-of select="."/>
                    </xsl:for-each-group>
                </xsl:variable>
            
                <xsl:for-each select="$start-dates/StartDate">
                    <Person>
                        <xsl:copy-of select="."/>
                        <xsl:variable name="next" select="following-sibling::StartDate[1]"/>   
                        <EndDate>
                            <xsl:value-of select="if ($next) then xs:date($next) - xs:dayTimeDuration('P1D') else '9999-12-31'"/>   
                         </EndDate>
                    </Person>
                </xsl:for-each>
                
            </xsl:copy>
        </xsl:for-each>
    </queryCompoundEmployeeResponse>
</xsl:template>

</xsl:stylesheet>

注意:这是一个快速草稿;它可能可以简化一点 - 可能是这样的:

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/queryCompoundEmployeeResponse">
    <queryCompoundEmployeeResponse>
        <xsl:for-each select="CompoundEmployee">
            <xsl:copy>
                <xsl:variable name="start-dates" select="distinct-values(Start_Dates/StartDate)"/>
                <xsl:for-each select="$start-dates">
                    <Person>
                        <StartDate>
                            <xsl:value-of select="."/>   
                        </StartDate>
                        <xsl:variable name="i" select="position()"/>   
                        <EndDate>                       
                            <xsl:value-of select="if ($i != last()) then xs:date($start-dates[$i+ 1]) - xs:dayTimeDuration('P1D') else '9999-12-31'"/>
                        </EndDate>
                    </Person>
                </xsl:for-each>
            </xsl:copy>
        </xsl:for-each>
    </queryCompoundEmployeeResponse>
</xsl:template>

</xsl:stylesheet>

【讨论】:

效果很好!我不想对 9999-12-31 进行硬编码,因此在开头使用了一个变量作为“ ” 并将其用于如果声明。非常感谢。有没有办法可以使用模板对这段代码进行模块化?对不起,我是 XSLT 的新手。尝试在我的原始代码中使用模板作为结束日期,但不太确定这个概念以及如何在循环中调用。再次感谢您! 恐怕我不知道“使用模板模块化”是什么意思。或者(更重要的是)你试图通过这个解决的问题是什么。 没有问题。代码工作得很好。我的意思是为这个 XSLT 中的各种代码块定义单独的模板,例如。分配开始/结束日期,收集其他节点信息(在我的 XSLT 中注释代码,然后使用应用模板来使用定义的模板,而不是将所有代码都放在一个模板中。在某处阅读使用模板是更好的编码方式XSLT。谢谢! 如果这些 cmets 没有任何意义,请忽略它们。正如我所说,我是一个新手,试图通过在线阅读以及与在线社区的互动和理解来学习。再次感谢您的帮助! 上例中的所有代码都是一个逻辑单元。将其拆分为不同的模板(如果可能的话,我不确定)除了让逻辑更难遵循之外什么也做不了(见GOTO。你可以在很多地方读到模板很好和xsl:for-each是邪恶的。不幸的是,这已经成为一种普遍的教条,尽管大部分情况下,如果不是完全没有根据的话。

以上是关于XSLT 2.0:根据 for-each -group 的当前索引评估和修改元素值的主要内容,如果未能解决你的问题,请参考以下文章

XSLT - 在嵌套的 for-each 中查找 position()

有没有办法在 XSLT 转换中用应用模板替换 for-each?

xslt for-each 和排序问题

从多个for-each XSLT段输出HTML

for-each 并使用 xslt 对 xml 进行排序和分组

更改上下文节点的 XSLT 指令/函数列表?