XSLT 2.0中的group-by是xsl:for-each-group元素的核心属性,专门用于实现XML节点的分组操作,大幅简化了同类数据的归类处理逻辑。它可以根据指定表达式的计算结果,将输入序列中的节点划分到不同的分组中,每个分组对应一个唯一的键值。

group-by基本语法
group-by需要配合xsl:for-each-group元素使用,基础语法结构如下:
<xsl:for-each-group select="节点序列" group-by="分组表达式">
<!-- 分组处理逻辑 -->
</xsl:for-each-group>
其中select用于指定需要分组的输入节点序列,group-by的表达式会对序列中的每个节点进行计算,表达式结果相同的节点会被归入同一个分组。每个分组会包含一个当前分组键和该分组下的所有节点,可通过内置函数获取相关信息。
常用内置分组相关函数
- current-grouping-key():返回当前分组的键值
- current-group():返回当前分组包含的所有节点序列
- position():返回当前分组在全部分组中的序号
基础分组示例
假设我们有如下XML数据,包含多个商品节点,每个商品有分类属性和价格子元素:
<products>
<product category="电子">
<name>手机</name>
<price>2999</price>
</product>
<product category="电子">
<name>平板</name>
<price>3999</price>
</product>
<product category="图书">
<name>编程指南</name>
<price>89</price>
</product>
<product category="图书">
<name>算法入门</name>
<price>79</price>
</product>
</products>
如果需要按商品分类对商品进行分组,并输出每个分类下的商品列表,XSLT模板可以这样编写:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<category_groups>
<!-- 按product的category属性分组 -->
<xsl:for-each-group select="products/product" group-by="@category">
<category_group>
<category_name><xsl:value-of select="current-grouping-key()"/></category_name>
<product_count><xsl:value-of select="count(current-group())"/></product_count>
<products>
<xsl:for-each select="current-group()">
<product>
<name><xsl:value-of select="name"/></name>
<price><xsl:value-of select="price"/></price>
</product>
</xsl:for-each>
</products>
</category_group>
</xsl:for-each-group>
</category_groups>
</xsl:template>
</xsl:stylesheet>
转换后的输出结果如下,可以看到电子类和图书类商品被分别归到两个分组中:
<category_groups>
<category_group>
<category_name>电子</category_name>
<product_count>2</product_count>
<products>
<product>
<name>手机</name>
<price>2999</price>
</product>
<product>
<name>平板</name>
<price>3999</price>
</product>
</products>
</category_group>
<category_group>
<category_name>图书</category_name>
<product_count>2</product_count>
<products>
<product>
<name>编程指南</name>
<price>89</price>
</product>
<product>
<name>算法入门</name>
<price>79</price>
</product>
</products>
</category_group>
</category_groups>
按子元素值分组
group-by的表达式不仅可以使用属性,也可以使用子元素的值作为分组依据。比如上面的商品数据,如果需要按价格区间分组,价格小于100的为低价商品,大于等于100的为高价商品,分组表达式可以这样写:
<xsl:for-each-group select="products/product" group-by="if (price < 100) then '低价' else '高价'">
<price_group>
<group_type><xsl:value-of select="current-grouping-key()"/></group_type>
<xsl:for-each select="current-group()">
<product>
<name><xsl:value-of select="name"/></name>
<price><xsl:value-of select="price"/></price>
</product>
</xsl:for-each>
</price_group>
</xsl:for-each-group>
这里group-by的表达式使用了条件判断,动态生成分组键值,实现了按自定义规则分组的效果。
与XSLT 1.0分组方式对比
XSLT 1.0没有原生的group-by功能,实现分组通常需要使用Muenchian分组法,核心是利用xsl:key定义键,再通过生成id的方式判断是否为分组的第一个节点,实现逻辑复杂且代码冗长。而XSLT 2.0的group-by只需要一行xsl:for-each-group就可以完成分组,代码可读性和开发效率都大幅提升。
以下是XSLT 1.0实现按分类分组的简化示例,对比可以看出group-by的优势:
<!-- XSLT 1.0 Muenchian分组示例 -->
<xsl:key name="category-key" match="product" use="@category"/>
<xsl:for-each select="products/product[generate-id() = generate-id(key('category-key', @category)[1])]">
<category_group>
<category_name><xsl:value-of select="@category"/></category_name>
<xsl:for-each select="key('category-key', @category)">
<!-- 输出商品信息 -->
</xsl:for-each>
</category_group>
</xsl:for-each>
使用注意事项
- group-by的表达式结果会被自动去重,每个唯一结果对应一个分组,空值也会作为一个有效的分组键
- 如果分组表达式对某个节点计算结果为空序列,该节点会被归入键值为空的分组中
- group-by仅支持XSLT 2.0及以上版本,使用时需要确保处理器支持XSLT 2.0规范,比如Saxon 9及以上版本
- 分组后的节点顺序默认和输入序列中第一个属于该分组的节点顺序一致,如需排序可以在
xsl:for-each-group内部或外部添加xsl:sort指令