使用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>
关键修改点
- 在
otherwise分支中新增变量$master-item和$update-item,分别获取当前merge组中master和update的item节点。 - 内部
xsl:merge的两个source分别引用这两个变量的descr节点,确保从两个文档的对应item中读取descr列表。 - 合并后的item节点显式复制id属性(也可使用
xsl:copy-of select="$master-item/@*"保留所有属性)。
修改后即可正确实现两层嵌套键的合并:先按item的@id合并,再对同一id的item内部按descr的@ref合并,重复的descr保留update中的内容,新增的descr也会被添加。
内容的提问来源于stack exchange,提问作者Clapas
相关产品推荐
相关产品推荐

