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

如何修改XSLT 1.0代码,实现相同ID元素合并并提取组内非空值?

问题:XSLT 1.0合并同ID元素并提取非空值及独有内容

需要用XSLT 1.0转换XML,实现合并相同ID的元素,提取同ID组内的非空值以及组中某元素独有的内容。当前编写的转换代码仅在同ID组的第一个元素包含所有非空值时生效。

示例中,第一个<payment>的<Amount>为空,但同ID组的第二个<payment>包含该元素;<Estimate>元素则相反;<Option>仅存在于同ID组的第一个<payment>中。

源XML

<?xml version="1.0" encoding="utf-8"?>
<Data>
    <payment Name="John Curtis">
        <Account Type="Personal" Id="111" Token="aaa"/>
        <Amount/>
        <Estimate Val="30"/>
        <Option Val="Yes"/>
    </payment>
    <payment Name="John Curtis">
        <Account Type="Personal" Id="111" Token="aaa"/>             
        <Amount Paid="10">
            <Breakout>
                <Misc Desc="Interest" Amount="8"/>
                <Misc Desc="Principal" Amount="2"/>
            </Breakout>
        </Amount>
        <Estimate/>
    </payment>
    <payment Name="Elvis Kans">
        <Account Type="Personal" Id="222" Token="bbb"/>
        <Amount Paid="5">
            <Breakout>
                <Misc Desc="Interest" Amount="3"/>
                <Misc Desc="Principal" Amount="2"/>
            </Breakout>
        </Amount>
        <Estimate Val="10"/>
        <Option Val="Yes"/>
    </payment>
</Data> 

预期输出

<?xml version="1.0" encoding="UTF-8"?>
<output>
   <result>
      <item VAL="Name">John Curtis</item>
      <group>
         <header VAL="Total">10</header>
         <item VAL="Item_1">8</item>
         <item VAL="Item_2">2</item>
      </group>
      <item VAL="Estimate">30</item>
      <item VAL="Option">Yes</item>
   </result>
   <result>
      <item VAL="Name">Elvis Kans</item>
      <group>
         <header VAL="Total">5</header>
         <item VAL="Item_1">3</item>
         <item VAL="Item_2">2</item>
      </group>
      <item VAL="Estimate">10</item>
      <item VAL="Option">Yes</item>
   </result>
</output>

现有XSLT 1.0代码

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:key name="paymentKey" match="payment" use="concat(Account/@Id, Account/@Token)"/>
    
    <xsl:template match="Data">
        <output>
            <xsl:apply-templates select="@*|payment[generate-id()=generate-id(key('paymentKey', concat(Account/@Id, Account/@Token))[1])]"/>
        </output>
    </xsl:template>
    
    <xsl:template match="payment">
        <xsl:variable name="curr-group" select="key('paymentKey', concat(Account/@Id, Account/@Token))"/>
        <result>
            <xsl:choose>
                <xsl:when test="count($curr-group)=1">
                    <!-- Different Groups -->
                    <xsl:element name="item">
                        <xsl:attribute name="VAL">Name</xsl:attribute>
                        <xsl:value-of select="@Name"/>
                    </xsl:element>
                    <group>
                        <xsl:element name="header">
                            <xsl:attribute name="VAL">Total</xsl:attribute>
                            <xsl:value-of select="Amount/@Paid"/>
                        </xsl:element>
                        <xsl:for-each select="Amount/Breakout/Misc">
                            <xsl:element name="item">
                                <xsl:attribute name="VAL">Item_<xsl:value-of select="position()"/>
                                </xsl:attribute>
                                <xsl:value-of select="@Amount"/>
                            </xsl:element>
                        </xsl:for-each>
                    </group>
                    <xsl:element name="item">
                        <xsl:attribute name="VAL">Estimate</xsl:attribute>
                        <xsl:value-of select="Estimate/@Val"/>
                    </xsl:element>
                    <xsl:element name="item">
                        <xsl:attribute name="VAL">Option</xsl:attribute>
                        <xsl:value-of select="Option/@Val"/>
                    </xsl:element>
                </xsl:when>
                <xsl:otherwise>
                    <!-- Same Groups -->
                    <xsl:element name="item">
                        <xsl:attribute name="VAL">Name</xsl:attribute>
                        <xsl:value-of select="@Name"/>
                    </xsl:element>
                    <group>
                        <xsl:element name="header">
                            <xsl:attribute name="VAL">Total</xsl:attribute>
                            <xsl:value-of select="Amount/@Paid"/>
                        </xsl:element>
                        <xsl:for-each select="Amount/Breakout/Misc">
                            <xsl:element name="item">
                                <xsl:attribute name="VAL">Item_<xsl:value-of select="position()"/>
                                </xsl:attribute>
                                <xsl:value-of select="@Amount"/>
                            </xsl:element>
                        </xsl:for-each>
                    </group>
                    <xsl:element name="item">
                        <xsl:attribute name="VAL">Estimate</xsl:attribute>
                        <xsl:value-of select="Estimate/@Val"/>
                    </xsl:element>
                    <xsl:element name="item">
                        <xsl:attribute name="VAL">Option</xsl:attribute>
                        <xsl:value-of select="Option/@Val"/>
                    </xsl:element>
                </xsl:otherwise>
            </xsl:choose>
        </result>
    </xsl:template>
</xsl:stylesheet>

修改方案

核心问题是当前代码只从当前匹配的payment元素(组内第一个元素)取值,未遍历整个组查找非空值。调整为从整个curr-group中提取有效数据,具体修改如下:

  1. 移除冗余的<xsl:choose>分支:单元素组和多元素组的处理逻辑可合并,统一从整个组取值。
  2. 从组内提取有效<Amount>数据:用$curr-group/Amount[@Paid][1]定位组内第一个带Paid属性的Amount元素。
  3. 提取非空<Estimate>值:用$curr-group/Estimate[@Val][1]/@Val获取组内第一个带Val属性的Estimate值。
  4. 提取存在的<Option>值:用$curr-group/Option[@Val][1]/@Val获取组内第一个存在的Option值。

修改后的完整XSLT代码:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:key name="paymentKey" match="payment" use="concat(Account/@Id, Account/@Token)"/>
    
    <xsl:template match="Data">
        <output>
            <xsl:apply-templates select="payment[generate-id()=generate-id(key('paymentKey', concat(Account/@Id, Account/@Token))[1])]"/>
        </output>
    </xsl:template>
    
    <xsl:template match="payment">
        <xsl:variable name="curr-group" select="key('paymentKey', concat(Account/@Id, Account/@Token))"/>
        <!-- 获取组内第一个有Paid属性的Amount元素 -->
        <xsl:variable name="valid-amount" select="$curr-group/Amount[@Paid][1]"/>
        <result>
            <item VAL="Name">
                <xsl:value-of select="@Name"/>
            </item>
            <group>
                <header VAL="Total">
                    <xsl:value-of select="$valid-amount/@Paid"/>
                </header>
                <xsl:for-each select="$valid-amount/Breakout/Misc">
                    <item VAL="Item_{position()}">
                        <xsl:value-of select="@Amount"/>
                    </item>
                </xsl:for-each>
            </group>
            <item VAL="Estimate">
                <xsl:value-of select="$curr-group/Estimate[@Val][1]/@Val"/>
            </item>
            <item VAL="Option">
                <xsl:value-of select="$curr-group/Option[@Val][1]/@Val"/>
            </item>
        </result>
    </xsl:template>
</xsl:stylesheet>

修改说明

  • valid-amount变量:精准定位组内有效Amount元素,确保获取到支付金额和拆分明细。
  • 提取Estimate和Option时,通过$curr-group/元素[@属性][1]的方式,从整个组中匹配第一个有效元素,避免仅从组内第一个payment取值。
  • 移除<xsl:choose>分支,简化代码结构,统一处理逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 14:21:31