You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XSLT遍历非同层级节点问题:for-each循环无法执行求解

问题:XSLT中for-each循环未执行的排查与解决

现有XML结构

<cashflows>
  <interestFlows>
    <interestPaymentPeriod>
        <calculationPeriod>
          <startDate>20230102</startDate>
          <endDate>20230130</endDate> 
        </calculationPeriod>
    </interestPaymentPeriod>
    <interestPaymentPeriod>
        <calculationPeriod>
          <startDate>20230202</startDate>
          <endDate>20230228</endDate> 
        </calculationPeriod>
    </interestPaymentPeriod>
    <interestPaymentPeriod>
        <calculationPeriod>
          <startDate>20230301</startDate>
          <endDate>20230330</endDate> 
        </calculationPeriod>
    </interestPaymentPeriod>
  </interestFlows>
  <capitalFlows>
    <flow>
       <flowSubType>AMO</flowSubType>
       <date>20230116</date>
    </flow>
    <flow>
       <flowSubType>AMO</flowSubType>
       <date>20230116</date>
    </flow> 
    <flow>
       <flowSubType>INT</flowSubType>
       <date>20230116</date>
    </flow>
  </capitalFlows>
</cashflows>

需求说明

针对每个flowSubType为AMO的flow节点,若其date处于某个interestPaymentPeriod的calculationPeriod起止日期范围内,则将该calculationPeriod拆分为[startDate; AMO date]和[AMO date; endDate]两个区块,预期输出如下:

<cashflows>
  <interestFlows>
    <interestPaymentPeriod>
        <calculationPeriod>
          <startDate>20230102</startDate>
          <endDate>20230116</endDate> 
        </calculationPeriod>
        <calculationPeriod>
          <startDate>20230116</startDate>
          <endDate>20230130</endDate> 
        </calculationPeriod>
    </interestPaymentPeriod>
    <interestPaymentPeriod>
        <calculationPeriod>
          <startDate>20230202</startDate>
          <endDate>20230220</endDate> 
        </calculationPeriod>
        <calculationPeriod>
          <startDate>20230220</startDate>
          <endDate>20230228</endDate> 
        </calculationPeriod>
    </interestPaymentPeriod>
    <interestPaymentPeriod>
        <calculationPeriod>
          <startDate>20230301</startDate>
          <endDate>20230330</endDate> 
        </calculationPeriod>
    </interestPaymentPeriod>
  </interestFlows>
  <capitalFlows>
    <flow>
       <flowSubType>AMO</flowSubType>
       <date>20230116</date>
    </flow>
    <flow>
       <flowSubType>AMO</flowSubType>
       <date>20230220</date>
    </flow> 
    <flow>
       <flowSubType>INT</flowSubType>
       <date>20230116</date>
    </flow>
  </capitalFlows>
</cashflows>

尝试的XSLT代码片段

<xsl:template match="interestPaymentPeriod/calculationPeriod">
       
    <xsl:variable name="found" select="'false'"></xsl:variable> 
    <xsl:variable name="pos" select="0"></xsl:variable> 
    <xsl:variable name="amortizingDate" select="''"></xsl:variable>
    <xsl:variable name="position" select="0"></xsl:variable> 
    <xsl:variable name="path" select="../../../capitalFlows/flow" />
    
    
   <xsl:for-each select="$path">
             <xsl:variable name="pos" select="$pos + 1"></xsl:variable>
          
      <xsl:if test="calculationStartDate &lt; (../../../capitalFlows/flow[position()=$pos and flowSubType = 'AMO']/exdividendDate) and (../../../capitalFlows/flow[position()=$pos and flowSubType = 'AMO']/exdividendDate) &lt; calculationEndDate ">
        <xsl:variable name="found" select="'true'"></xsl:variable> 
        <xsl:variable name="position" select="$pos"></xsl:variable> 
        
      </xsl:if>
      
      <xsl:if test="calculationStartDate &lt; (../../../capitalFlows/flow[flowSubType = 'AMO']/exdividendDate) and (../../../capitalFlows/flow[flowSubType = 'AMO']/exdividendDate) &lt; calculationEndDate ">    
         <xsl:variable name="found" select="'false'"></xsl:variable>    
      </xsl:if> 
         
 </xsl:for-each>


.....

遇到的问题

变量$path能正确返回3个flow节点,但xsl:for-each select="$path"循环完全无法进入执行。


解决方案

1. 循环不执行的直接原因排查

你的代码存在几个关键错误,导致循环逻辑失效:

  • XSLT变量不可变性:在xsl:for-each内部重新定义$pos、$found等变量时,不会改变外部变量的值——XSLT中变量是只读的,每次重新定义都是新的局部变量,和外部变量无关联。
  • 节点名与路径错误:
    • XML中不存在calculationStartDate、calculationEndDate、exdividendDate节点,正确名称应为startDate、endDate、date。
    • xsl:for-each内部上下文是flow节点,无需再用../../../capitalFlows/flow定位,直接访问当前节点的子节点即可。
  • 大小写问题:代码中capitalflows和XML里的capitalFlows大小写不一致,严格来说会导致路径匹配失败。

2. 重构后的完整XSLT实现(XSLT 1.0)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
    <xsl:output method="xml" indent="yes"/>
    
    <!-- 身份模板:复制所有未匹配到特殊处理逻辑的节点 -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <!-- 处理需要拆分的calculationPeriod节点 -->
    <xsl:template match="calculationPeriod">
        <!-- 获取当前计算周期的起止日期 -->
        <xsl:variable name="currentStart" select="startDate"/>
        <xsl:variable name="currentEnd" select="endDate"/>
        
        <!-- 收集当前周期内所有AMO日期并去重排序 -->
        <xsl:variable name="amoDates">
            <xsl:for-each select="/cashflows/capitalFlows/flow[flowSubType='AMO' and date > $currentStart and date < $currentEnd]">
                <xsl:sort select="date"/>
                <date><xsl:value-of select="date"/></date>
            </xsl:for-each>
        </xsl:variable>
        <xsl:variable name="uniqueAmoDates" select="msxsl:node-set($amoDates)/date[not(.=preceding-sibling::date)]"/>
        
        <!-- 根据是否有符合条件的AMO日期,决定拆分或保留原周期 -->
        <xsl:choose>
            <xsl:when test="$uniqueAmoDates">
                <!-- 第一个子周期:原开始日期到第一个AMO日期 -->
                <calculationPeriod>
                    <startDate><xsl:value-of select="$currentStart"/></startDate>
                    <endDate><xsl:value-of select="$uniqueAmoDates[1]"/></endDate>
                </calculationPeriod>
                
                <!-- 中间子周期:多个AMO日期之间的区间(如果存在) -->
                <xsl:for-each select="$uniqueAmoDates[position() > 1]">
                    <calculationPeriod>
                        <startDate><xsl:value-of select="preceding-sibling::date[1]"/></startDate>
                        <endDate><xsl:value-of select="."/></endDate>
                    </calculationPeriod>
                </xsl:for-each>
                
                <!-- 最后一个子周期:最后一个AMO日期到原结束日期 -->
                <calculationPeriod>
                    <startDate><xsl:value-of select="$uniqueAmoDates[last()]"/></startDate>
                    <endDate><xsl:value-of select="$currentEnd"/></endDate>
                </calculationPeriod>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy>
                    <xsl:apply-templates select="@*|node()"/>
                </xsl:copy>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

3. 关键优化点说明

  • 使用身份模板减少重复代码,仅对需要修改的calculationPeriod节点做特殊处理。
  • 先收集并去重符合条件的AMO日期,避免重复拆分同一周期。
  • 若使用XSLT 2.0+,可直接用序列处理日期集合,无需msxsl:node-set()函数。
  • 正确利用上下文节点,避免冗余的路径定位。

内容的提问来源于stack exchange,提问作者Bill

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 17:35:15