如何通过XSLT的for-each将批量元素归集到单个Batch元素中
XSLT批量记录归集到单Batch元素修复方案
问题根因
现有逐<Test>节点遍历生成<Batch>的逻辑是单Test节点对单Batch的1:1映射,未做批次维度的归组处理,必然导致同批次的BatchH、Entry、NOC、BatchC节点分散到多个独立的Batch元素中,同时因为遍历逻辑未做空值判断、层级对齐处理,会衍生空Batch标签、节点嵌套错误问题。
修复核心逻辑
- 以批次唯一标识(通常为BatchH下的批次号字段,可根据自身XSD定义调整)为维度做分组,而非逐Test节点生成Batch
- 单个分组(单个批次)仅生成1个
<Batch>父元素,父元素内部严格按照目标XSD要求的顺序归集子节点:先写入批次头BatchH,再批量写入该批次下所有Entry、NOC记录,最后写入批次尾BatchC - 所有节点写入前做存在性校验,过滤空内容,从根源避免空标签生成
可直接复用的代码实现
XSLT 2.0及以上版本(大部分SOA集成场景默认支持)
直接用内置的xsl:for-each-group标签实现分组,逻辑简洁性能更好:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="no"/> <!-- 匹配源XML根节点,根节点名称可根据实际源XSD调整 --> <xsl:template match="/SourceRoot"> <!-- 目标XML根节点,可根据实际目标XSD调整 --> <TargetRoot> <!-- 按批次唯一键分组,此处group-by字段替换为自身Schema里的批次唯一标识即可 --> <xsl:for-each-group select="Test" group-by="BatchH/BatchNumber"> <Batch> <!-- 写入当前批次头,取分组内第一个Test节点下的BatchH即可 --> <xsl:copy-of select="current-group()[1]/BatchH"/> <!-- 归集当前批次下所有Entry记录 --> <xsl:for-each select="current-group()/Entry[normalize-space(.) != '']"> <xsl:copy-of select="."/> </xsl:for-each> <!-- 归集当前批次下所有NOC记录 --> <xsl:for-each select="current-group()/NOC[normalize-space(.) != '']"> <xsl:copy-of select="."/> </xsl:for-each> <!-- 写入当前批次尾,取分组内最后一个Test节点下的BatchC即可 --> <xsl:copy-of select="current-group()[last()]/BatchC"/> </Batch> </xsl:for-each-group> </TargetRoot> </xsl:template> </xsl:stylesheet>
XSLT 1.0版本兼容方案
如果运行环境仅支持XSLT 1.0,用Muenchian分组法替换分组逻辑即可,内部子节点归集逻辑和2.0版本一致:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <!-- 定义批次分组键,use属性替换为自身Schema的批次唯一标识 --> <xsl:key name="batchGroupKey" match="Test" use="BatchH/BatchNumber"/> <xsl:template match="/SourceRoot"> <TargetRoot> <!-- 遍历每个批次的首个Test节点,作为分组入口 --> <xsl:for-each select="//Test[generate-id() = generate-id(key('batchGroupKey', BatchH/BatchNumber)[1])]"> <xsl:variable name="currentBatchId" select="BatchH/BatchNumber"/> <Batch> <xsl:copy-of select="BatchH"/> <!-- 归集当前批次下所有非空Entry --> <xsl:for-each select="key('batchGroupKey', $currentBatchId)/Entry[normalize-space(.) != '']"> <xsl:copy-of select="."/> </xsl:for-each> <!-- 归集当前批次下所有非空NOC --> <xsl:for-each select="key('batchGroupKey', $currentBatchId)/NOC[normalize-space(.) != '']"> <xsl:copy-of select="."/> </xsl:for-each> <xsl:copy-of select="key('batchGroupKey', $currentBatchId)[last()]/BatchC"/> </Batch> </xsl:for-each> </TargetRoot> </xsl:template> </xsl:stylesheet>
校验规则
转换完成后对照以下规则校验,确保输出符合XSD要求:
- 每个批次仅对应1个
<Batch>父元素,无重复、无遗漏 - Batch内子节点顺序严格匹配目标XSD要求:BatchH → Entry → NOC → BatchC,无嵌套错位
- 无空
<Batch>标签、无空白无效子节点 - 所有节点命名、层级完全对齐目标XSD定义
如果你的源XML里批次唯一标识字段、节点命名和示例有差异,直接替换代码中对应字段、节点名即可,核心分组归组逻辑不需要调整。
内容的提问来源于stack exchange,提问作者Nilesh Jha
相关产品推荐
相关产品推荐

