如何基于子元素分组复制XML且不丢失同级数据(XSLT 2.0实现)
XSLT 2.0 根据Contact的@name拆分Record并分组交易元素
看起来你需要把单个Record按照交易元素(To_Transaction和From_Transaction)里的Contact/@name拆分成多条记录,同时保留所有非交易的公共元素。你的尝试方向是对的,但分组逻辑没找对路子,我来帮你修正一下。
核心思路
我们需要:
- 提取所有交易中
Contact/@name的唯一值,每个值对应一条新的Record - 对每个唯一的name,复制原
Record中的公共元素(Name、Due_Date、Customer等) - 把所有属于该name的
To_Transaction和From_Transaction都放到对应的新Record里
简洁的实现方案
用for-each-group直接对交易元素分组是最直观的方式,代码如下:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <!-- 标识模板:默认复制所有节点和属性,确保非交易元素的结构完整保留 --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <!-- 专门处理Record节点,实现拆分逻辑 --> <xsl:template match="Record"> <!-- 将所有交易元素按Contact的name属性分组 --> <xsl:for-each-group select="To_Transaction|From_Transaction" group-by="Contact/@name"> <!-- 为每个分组创建新的Record节点 --> <Record> <!-- 复制原Record中所有非交易类型的子元素(公共部分) --> <xsl:copy-of select="../*[not(self::To_Transaction or self::From_Transaction)]"/> <!-- 复制当前分组下的所有交易元素 --> <xsl:copy-of select="current-group()"/> </Record> </xsl:for-each-group> </xsl:template> </xsl:stylesheet>
代码解释
- 标识模板:负责复制XML中大部分节点和属性,保证
Name、Customer等公共元素的结构和内容完全保留,不用单独处理每个公共元素。 - Record模板:
for-each-group select="To_Transaction|From_Transaction":选中当前Record下的所有交易元素group-by="Contact/@name":指定按Contact的name属性作为分组依据current-group():获取当前分组内的所有交易元素,直接复制到新Record中../*[not(self::To_Transaction or self::From_Transaction)]:向上找到原Record,复制所有不是交易元素的子节点(公共部分)
另一种实现方式(先获取唯一name)
如果你更喜欢先明确获取所有唯一的name值,再逐个生成Record,也可以用下面的写法,效果完全一致:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <xsl:template match="Report"> <xsl:copy> <!-- 提取所有交易中Contact@name的唯一值 --> <xsl:variable name="unique-names" select="distinct-values(Record/(To_Transaction|From_Transaction)/Contact/@name)"/> <!-- 遍历每个唯一name生成Record --> <xsl:for-each select="$unique-names"> <xsl:variable name="current-name" select="."/> <Record> <!-- 复制公共元素 --> <xsl:copy-of select="/Report/Record/*[not(self::To_Transaction or self::From_Transaction)]"/> <!-- 筛选并复制当前name对应的To_Transaction --> <xsl:copy-of select="/Report/Record/To_Transaction[Contact/@name = $current-name]"/> <!-- 筛选并复制当前name对应的From_Transaction --> <xsl:copy-of select="/Report/Record/From_Transaction[Contact/@name = $current-name]"/> </Record> </xsl:for-each> </xsl:copy> </xsl:template> </xsl:stylesheet>
你原来代码的问题
你之前的group-by="(To_Transaction,'*')"逻辑不正确,这个分组键无法正确提取交易元素的Contact/@name,而且没有针对交易元素进行分组,所以无法实现拆分效果。上面的两种写法都解决了这个问题,你可以根据自己的习惯选择。
内容的提问来源于stack exchange,提问作者user2852812
相关产品推荐
相关产品推荐

