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 < (../../../capitalFlows/flow[position()=$pos and flowSubType = 'AMO']/exdividendDate) and (../../../capitalFlows/flow[position()=$pos and flowSubType = 'AMO']/exdividendDate) < calculationEndDate "> <xsl:variable name="found" select="'true'"></xsl:variable> <xsl:variable name="position" select="$pos"></xsl:variable> </xsl:if> <xsl:if test="calculationStartDate < (../../../capitalFlows/flow[flowSubType = 'AMO']/exdividendDate) and (../../../capitalFlows/flow[flowSubType = 'AMO']/exdividendDate) < 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定位,直接访问当前节点的子节点即可。
- XML中不存在
- 大小写问题:代码中
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
相关产品推荐
相关产品推荐

