如何使用XSLT for-each循环处理数量可变的多实体XML转换?
没问题,这个动态分组的XML转换需求完全可以用XSLT搞定!我给你两种方案,分别适配不同版本的XSLT环境,都能完美处理SubData节点数量不固定的场景。
方案一:XSLT 1.0 兼容版(Muenchian分组)
这个方案用经典的Muenchian分组技巧,几乎所有XSLT处理器都支持:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 定义分组键:提取节点名末尾的数字作为分组依据 --> <xsl:key name="group-by-number" match="*[starts-with(local-name(), 'Name') or starts-with(local-name(), 'Weight') or starts-with(local-name(), 'Size')]" use="substring-after(local-name(), translate(local-name(), '0123456789', ''))"/> <!-- 匹配根xml节点 --> <xsl:template match="/xml"> <xml> <!-- 原样保留所有Header开头的节点 --> <xsl:copy-of select="*[starts-with(local-name(), 'Header')]"/> <!-- 遍历所有唯一的数字分组(以Name节点为锚点) --> <xsl:for-each select="*[starts-with(local-name(), 'Name')][count(. | key('group-by-number', substring-after(local-name(), 'Name'))[1]) = 1]"> <!-- 提取当前分组的数字后缀 --> <xsl:variable name="num" select="substring-after(local-name(), 'Name')"/> <!-- 动态生成SubDataN节点 --> <xsl:element name="SubData{$num}"> <!-- 重命名并复制Name节点内容 --> <Name><xsl:value-of select="."/></Name> <!-- 匹配对应数字的Weight节点并重命名 --> <Weight><xsl:value-of select="../*[local-name() = concat('Weight', $num)]"/></Weight> <!-- 匹配对应数字的Size节点并重命名 --> <Size><xsl:value-of select="../*[local-name() = concat('Size', $num)]"/></Size> </xsl:element> </xsl:for-each> </xml> </xsl:template> </xsl:stylesheet>
核心逻辑说明:
- 分组规则:通过
<xsl:key>提取节点名里的数字部分(比如Name1里的1),把同数字的NameN/WeightN/SizeN分到一组。 - 动态节点名:用
<xsl:element name="SubData{$num}"/>自动生成SubData1、SubData2这类节点,不管数字是多少都能适配。 - 通配符匹配:用
starts-with()和concat()组合,不用硬编码数字,完美支持数量不固定的SubData节点。
方案二:XSLT 2.0+ 简洁版(正则分组)
如果你的环境支持XSLT 2.0及以上(比如Saxon处理器),可以用正则表达式简化代码:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/xml"> <xml> <!-- 保留所有Header节点 --> <xsl:copy-of select="*[starts-with(local-name(), 'Header')]"/> <!-- 按节点名的数字后缀分组 --> <xsl:for-each-group select="*[matches(local-name(), '^(Name|Weight|Size)\d+$')]" group-by="replace(local-name(), '^(Name|Weight|Size)(\d+)$', '$2')"> <!-- 生成对应SubData节点 --> <xsl:element name="SubData{current-grouping-key()}"> <Name><xsl:value-of select="current-group()[local-name() = concat('Name', current-grouping-key())]"/></Name> <Weight><xsl:value-of select="current-group()[local-name() = concat('Weight', current-grouping-key())]"/></Weight> <Size><xsl:value-of select="current-group()[local-name() = concat('Size', current-grouping-key())]"/></Size> </xsl:element> </xsl:for-each-group> </xml> </xsl:template> </xsl:stylesheet>
核心逻辑说明:
- 正则匹配:用
matches()筛选出带数字后缀的目标节点,replace()直接提取数字作为分组键。 - 分组简化:
for-each-group直接按数字分组,代码更简洁易读。
这两种方案都能自动适配任意数量的SubData节点,不用修改代码就能处理新增的Name4/Weight4/Size4这类节点。
内容的提问来源于stack exchange,提问作者bluefox
相关产品推荐
相关产品推荐

