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

使用XSLT 3.0的xsl:merge指令合并XML时嵌套键合并异常

问题描述

尝试使用XSLT 3.0的xsl:merge指令合并两个同结构XML文档,单层键合并正常,但处理两层嵌套键(item的@id和descr的@ref)时,当item同时存在于两个文档中,合并结果不符合预期。

测试XML文件

list1.xml

<list>
   <item id="1">
      <descr ref="a"/>
      <descr ref="b"/>
   </item>
   <item id="2">
      <descr ref="c"/>
      <descr ref="d"/>
   </item>
   <item id="3">
      <descr ref="e">one</descr>
      <descr ref="f">one</descr>
   </item>
</list>

list2.xml

<list>
   <item id="3">
      <descr ref="e">two</descr>
      <descr ref="f">two</descr>
      <descr ref="x">two</descr>
   </item>
   <item id="4">
      <descr ref="z"/>
      <descr ref="y"/>
   </item>
</list>

当前XSLT代码

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.1">
   <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
   <xsl:mode on-no-match="shallow-copy"/>
   <xsl:template match="/">
      <list>
         <xsl:merge>
            <xsl:merge-source name="master" for-each-source="'list1.xml'" sort-before-merge="yes" select="/list/item">
               <xsl:merge-key select="@id" order="ascending"/>
            </xsl:merge-source>
            <xsl:merge-source name="update" for-each-source="'list2.xml'" sort-before-merge="yes" select="/list/item">
               <xsl:merge-key select="@id" order="ascending"/>
            </xsl:merge-source>
            <xsl:merge-action>
               <xsl:choose>
                  <xsl:when test="empty(current-merge-group('update'))">
                     <xsl:copy-of select="current-merge-group('master')"/>
                     <xsl:message>master!</xsl:message>
                  </xsl:when>
                  <xsl:when test="empty(current-merge-group('master'))">
                     <xsl:copy-of select="current-merge-group('update')"/>
                     <xsl:message>update!</xsl:message>
                  </xsl:when>
                  <xsl:otherwise>
                     <item>
                        <xsl:merge>
                           <xsl:merge-source name="descr-master" sort-before-merge="yes" select="descr">
                              <xsl:merge-key select="@ref" order="ascending"/>
                           </xsl:merge-source>
                           <xsl:merge-source name="descr-update" sort-before-merge="yes" select="descr">
                              <xsl:merge-key select="@ref" order="ascending"/>
                           </xsl:merge-source>
                           <xsl:merge-action>
                              <xsl:choose>
                                 <xsl:when test="empty(current-merge-group('descr-update'))">
                                    <xsl:copy-of select="current-merge-group('descr-master')"/>
                                    <xsl:message>descr master!</xsl:message>
                                 </xsl:when>
                                 <xsl:when test="empty(current-merge-group('descr-master'))">
                                    <xsl:copy-of select="current-merge-group('descr-update')"/>
                                    <xsl:message>descr update!</xsl:message>
                                 </xsl:when>
                                 <xsl:otherwise>
                                    <xsl:copy-of select="current-merge-group('descr-update')"/>
                                    <xsl:message>present in both: kept update!</xsl:message>
                                 </xsl:otherwise>
                              </xsl:choose>
                           </xsl:merge-action>
                        </xsl:merge>
                     </item>
                  </xsl:otherwise>
               </xsl:choose>
            </xsl:merge-action>
         </xsl:merge>
      </list>
   </xsl:template>
</xsl:stylesheet>

问题现象

当item仅存在于其中一个文档时,合并结果正确;但当item同时存在于两个文档时(如id="3"的item),内部的descr合并逻辑未按预期执行,无法得到期望结果。

期望合并结果

<list>
   <item id="1">
      <descr ref="a"/>
      <descr ref="b"/>
   </item>
   <item id="2">
      <descr ref="c"/>
      <descr ref="d"/>
   </item>
   <item id="3">
      <descr ref="e">two</descr>
      <descr ref="f">two</descr>
      <descr ref="x">two</descr>
   </item>
   <item id="4">
      <descr ref="z"/>
      <descr ref="y"/>
   </item>
</list>

问题原因与解决方案

问题根源

在内部的xsl:merge中,两个xsl:merge-source的select="descr"基于当前上下文节点,但此时上下文是外层merge-group中的第一个节点(默认是master中的item),导致两个merge-source都读取同一个item的descr,没有分别指向master和update中的对应item。

修改后的XSLT代码

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.1">
   <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
   <xsl:mode on-no-match="shallow-copy"/>
   <xsl:template match="/">
      <list>
         <xsl:merge>
            <xsl:merge-source name="master" for-each-source="'list1.xml'" sort-before-merge="yes" select="/list/item">
               <xsl:merge-key select="@id" order="ascending"/>
            </xsl:merge-source>
            <xsl:merge-source name="update" for-each-source="'list2.xml'" sort-before-merge="yes" select="/list/item">
               <xsl:merge-key select="@id" order="ascending"/>
            </xsl:merge-source>
            <xsl:merge-action>
               <xsl:choose>
                  <xsl:when test="empty(current-merge-group('update'))">
                     <xsl:copy-of select="current-merge-group('master')"/>
                  </xsl:when>
                  <xsl:when test="empty(current-merge-group('master'))">
                     <xsl:copy-of select="current-merge-group('update')"/>
                  </xsl:when>
                  <xsl:otherwise>
                     <!-- 分别获取master和update中的对应item -->
                     <xsl:variable name="master-item" select="current-merge-group('master')[1]"/>
                     <xsl:variable name="update-item" select="current-merge-group('update')[1]"/>
                     <item id="{$master-item/@id}">
                        <xsl:merge>
                           <xsl:merge-source name="descr-master" sort-before-merge="yes" select="$master-item/descr">
                              <xsl:merge-key select="@ref" order="ascending"/>
                           </xsl:merge-source>
                           <xsl:merge-source name="descr-update" sort-before-merge="yes" select="$update-item/descr">
                              <xsl:merge-key select="@ref" order="ascending"/>
                           </xsl:merge-source>
                           <xsl:merge-action>
                              <xsl:choose>
                                 <xsl:when test="empty(current-merge-group('descr-update'))">
                                    <xsl:copy-of select="current-merge-group('descr-master')"/>
                                 </xsl:when>
                                 <xsl:when test="empty(current-merge-group('descr-master'))">
                                    <xsl:copy-of select="current-merge-group('descr-update')"/>
                                 </xsl:when>
                                 <xsl:otherwise>
                                    <xsl:copy-of select="current-merge-group('descr-update')"/>
                                 </xsl:otherwise>
                              </xsl:choose>
                           </xsl:merge-action>
                        </xsl:merge>
                     </item>
                  </xsl:otherwise>
               </xsl:choose>
            </xsl:merge-action>
         </xsl:merge>
      </list>
   </xsl:template>
</xsl:stylesheet>

关键修改点

  1. 在otherwise分支中新增变量$master-item和$update-item,分别获取当前merge组中master和update的item节点。
  2. 内部xsl:merge的两个source分别引用这两个变量的descr节点,确保从两个文档的对应item中读取descr列表。
  3. 合并后的item节点显式复制id属性(也可使用xsl:copy-of select="$master-item/@*"保留所有属性)。

修改后即可正确实现两层嵌套键的合并:先按item的@id合并,再对同一id的item内部按descr的@ref合并,重复的descr保留update中的内容,新增的descr也会被添加。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:47:34