0

0

XSLT如何对节点进行分组操作?

小老鼠

小老鼠

发布时间:2025-08-24 15:05:01

|

403人浏览过

|

来源于php中文网

原创

XSLT分组主要有两种方式:XSLT 2.0+使用for-each-group指令,通过group-by等属性实现直观高效的分组;XSLT 1.0则依赖Muenchian Grouping,利用key()和generate-id()筛选每组首个节点,虽复杂但有效。

xslt如何对节点进行分组操作?

XSLT对节点进行分组操作,核心上来说,主要有两种主流方式:对于XSLT 2.0及更高版本,我们有强大的

for-each-group
指令,它极大地简化了分组逻辑;而在XSLT 1.0时代,则需要依赖一种被称为Muenchian Grouping(门兴分组)的技巧,通过
key()
函数和
generate-id()
的组合来实现。

解决方案

要对XSLT中的节点进行分组,我们通常会根据某个节点的特定属性值、子节点内容或者计算出的某个键值来组织相关的节点集合。这在数据转换中非常常见,比如把扁平化的数据转换成带有层级结构的报告。

1. XSLT 2.0+ 中的

for-each-group
指令

这是现代XSLT处理分组的首选方式,它直观且强大。

for-each-group
允许你指定一个节点集合,然后根据一个表达式对这些节点进行分组。

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/root">
    <grouped-data>
      <!-- 根据'category'属性进行分组 -->
      <xsl:for-each-group select="item" group-by="@category">
        <category-group name="{current-grouping-key()}">
          <xsl:for-each select="current-group()">
            <item-detail id="{@id}">
              <name><xsl:value-of select="name"/></name>
              <price><xsl:value-of select="price"/></price>
            </item-detail>
          </xsl:for-each>
        </category-group>
      </xsl:for-each-group>
    </grouped-data>
  </xsl:template>

</xsl:stylesheet>

对应这个XML输入:

<root>
  <item id="A001" category="Electronics">
    <name>Laptop</name>
    <price>1200</price>
  </item>
  <item id="A002" category="Books">
    <name>XSLT Cookbook</name>
    <price>45</price>
  </item>
  <item id="A003" category="Electronics">
    <name>Mouse</name>
    <price>25</price>
  </item>
  <item id="A004" category="Books">
    <name>XML Basics</name>
    <price>30</price>
  </item>
</root>

输出会是:

<grouped-data>
   <category-group name="Electronics">
      <item-detail id="A001">
         <name>Laptop</name>
         <price>1200</price>
      </item-detail>
      <item-detail id="A003">
         <name>Mouse</name>
         <price>25</price>
      </item-detail>
   </category-group>
   <category-group name="Books">
      <item-detail id="A002">
         <name>XSLT Cookbook</name>
         <price>45</price>
      </item-detail>
      <item-detail id="A004">
         <name>XML Basics</name>
         <price>30</price>
      </item-detail>
   </category-group>
</grouped-data>

group-by
属性定义了分组的依据。
current-grouping-key()
用于获取当前组的键值,而
current-group()
则返回当前组中的所有节点。

2. XSLT 1.0 中的 Muenchian Grouping(键控分组)

在没有

for-each-group
的日子里,Muenchian Grouping是实现分组的“黑魔法”。它利用了
key()
函数能够高效查找节点,以及
generate-id()
函数为每个节点生成唯一ID的特性。其核心思想是:只处理每个分组的“第一个”节点。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes"/>

  <!-- 定义一个键,用于按'category'属性查找item节点 -->
  <xsl:key name="items-by-category" match="item" use="@category"/>

  <xsl:template match="/root">
    <grouped-data>
      <!-- 遍历所有item节点,但只选择每个分组的第一个节点 -->
      <xsl:for-each select="item[count(. | key('items-by-category', @category)[1]) = 1]">
        <xsl:variable name="currentCategory" select="@category"/>
        <category-group name="{$currentCategory}">
          <!-- 遍历当前分组的所有节点 -->
          <xsl:for-each select="key('items-by-category', $currentCategory)">
            <item-detail id="{@id}">
              <name><xsl:value-of select="name"/></name>
              <price><xsl:value-of select="price"/></price>
            </item-detail>
          </xsl:for-each>
        </category-group>
      </xsl:for-each>
    </grouped-data>
  </xsl:template>

</xsl:stylesheet>

使用与上面相同的XML输入,输出结果会是一致的。这里的

item[count(. | key('items-by-category', @category)[1]) = 1]
是Muenchian Grouping的精髓,它巧妙地筛选出每个分组的“领导者”节点。

XSLT 2.0+中如何使用
for-each-group
进行高效分组?

老实说,自从XSLT 2.0引入

for-each-group
,分组操作简直是鸟枪换炮,变得异常直观和强大。我个人觉得,这玩意儿是XSLT 2.0最令人拍案叫绝的特性之一,它把之前那些需要绞尽脑汁才能实现的复杂逻辑,一下子拉到了“所见即所得”的层面。

for-each-group
指令的核心在于它的几个关键属性:

  • select
    : 指定你要分组的节点集合。比如
    select="item"
    就是选择所有名为
    item
    的节点。
  • group-by
    : 这是最常用的分组方式,根据一个XPath表达式的值来分组。所有该表达式值相同的节点会被分到同一个组。
  • group-starting-with
    : 这是一个非常灵活的选项,它定义了新组的开始条件。当遇到符合这个条件的节点时,一个新的组就会从它开始。这对于处理“非结构化”的兄弟节点序列特别有用,比如HTML文档中,一个
    <h3>
    后面跟着若干个
    <p>
    ,直到下一个
    <h3>
    出现。
  • group-ending-with
    : 类似于
    group-starting-with
    ,但它定义的是一个组的结束条件。
  • group-adjacent
    : 这个属性用于对相邻的、具有相同键值的节点进行分组。它和
    group-by
    有点像,但更强调“相邻”这个概念,如果中间隔了其他键值的节点,即使后面有相同键值的节点,也不会归为同一个组。

for-each-group
内部,有两个非常重要的函数:

  • current-group()
    : 返回当前正在处理的组中的所有节点。你可以像遍历普通节点集一样遍历它们。
  • current-grouping-key()
    : 返回当前组的键值。这对于在组头显示分组信息非常有用。

举个更复杂的例子,我们想分组销售订单,先按年份,再按月份:

XML输入:

琅琅配音
琅琅配音

全能AI配音神器

下载
<sales>
  <order id="1" date="2023-01-15" amount="100"/>
  <order id="2" date="2023-02-20" amount="150"/>
  <order id="3" date="2023-01-25" amount="120"/>
  <order id="4" date="2022-11-01" amount="80"/>
  <order id="5" date="2023-02-10" amount="200"/>
</sales>

XSLT:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/sales">
    <yearly-sales>
      <!-- 第一层分组:按年份 -->
      <xsl:for-each-group select="order" group-by="substring(@date, 1, 4)">
        <year-group year="{current-grouping-key()}">
          <!-- 第二层分组:在当前年份组内,再按月份分组 -->
          <xsl:for-each-group select="current-group()" group-by="substring(@date, 6, 2)">
            <month-group month="{current-grouping-key()}">
              <total-amount><xsl:value-of select="sum(current-group()/@amount)"/></total-amount>
              <orders-in-month>
                <xsl:for-each select="current-group()">
                  <order-summary id="{@id}" date="{@date}" amount="{@amount}"/>
                </xsl:for-each>
              </orders-in-month>
            </month-group>
          </xsl:for-each-group>
        </year-group>
      </xsl:for-each-group>
    </yearly-sales>
  </xsl:template>

</xsl:stylesheet>

这个例子展示了嵌套分组的强大。我们先按年份

substring(@date, 1, 4)
分组,然后在每个年份组内,又对
current-group()
(即当前年份的所有订单)进行月份
substring(@date, 6, 2)
分组。这种层层递进的逻辑,用
for-each-group
来表达简直是水到渠成,写起来也相当顺手。

XSLT 1.0环境下,Muenchian分组模式的实现与局限性

Muenchian Grouping,这个名字听起来有点酷,但它的实现方式,对于初学者来说,绝对是XSLT 1.0时代的一个“智力挑战”。它不是一个内置指令,而是一种巧妙地利用XSLT 1.0固有功能的模式。我记得刚开始学XSLT 1.0的时候,理解这个模式花了我不少时间,因为它确实有点反直觉。

核心思想:

  1. 定义键(
    xsl:key
    : 使用
    xsl:key
    来创建一个索引,将所有需要分组的节点与它们的“分组键”关联起来。
  2. 找到每个组的“头”节点: 这是最关键的一步。我们遍历所有节点,但只选择那些在
    key()
    函数返回的节点集中,它自己就是第一个节点的。
    count(. | key('your-key', your-criteria)[1]) = 1
    就是这个魔法表达式。
    • key('your-key', your-criteria)
      :返回所有符合
      your-criteria
      的节点。
    • key('your-key', your-criteria)[1]
      :返回这些节点中的第一个。
    • count(. | ...)
      :这是一个集合运算,计算当前节点和第一个节点合并后的节点数量。如果当前节点就是第一个节点,那么合并后节点数量是1;如果不是,合并后节点数量是2。所以,
      = 1
      就筛选出了每个组的第一个节点。
  3. 遍历组内成员: 找到组头后,再次使用
    key()
    函数,传入组头的键值,就能获取该组的所有成员。

我们还是用之前的商品分类XML来演示Muenchian Grouping:

<root>
  <item id="A001" category="Electronics">
    <name>Laptop</name>
    <price>1200</price>
  </item>
  <item id="A002" category="Books">
    <name>XSLT Cookbook</name>
    <price>45</price>
  </item>
  <item id="A003" category="Electronics">
    <name>Mouse</name>
    <price>25</price>
  </item>
  <item id="A004" category="Books">
    <name>XML Basics</name>
    <price>30</price>
  </item>
</root>

XSLT 1.0 (Muenchian Grouping):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes"/>

  <!-- 定义一个键,用于按'category'属性查找item节点 -->
  <xsl:key name="items-by-category" match="item" use="@category"/>

  <xsl:template match="/root">
    <grouped-data>
      <!-- 遍历所有item节点,但只选择每个分组的第一个节点 -->
      <xsl:for-each select="item[count(. | key('items-by-category', @category)[1]) = 1]">
        <xsl:variable name="currentCategory" select="@category"/>
        <category-group name="{$currentCategory}">
          <!-- 遍历当前分组的所有节点 -->
          <xsl:for-each select="key('items-by-category', $currentCategory)">
            <item-detail id="{@id}">
              <name><xsl:value-of select="name"/></name>
              <price><xsl:value-of select="price"/></price>
            </item-detail>
          </xsl:for-each>
        </category-group>
      </xsl:for-each>
    </grouped-data>
  </xsl:template>

</xsl:stylesheet>

局限性:

  1. 复杂性高: 表达式
    item[count(. | key('items-by-category', @category)[1]) = 1]
    对于不熟悉XSLT 1.0技巧的人来说,确实难以理解和记忆。维护起来也容易出错。
  2. 可读性差: 相比
    for-each-group
    的语义化,Muenchian Grouping的代码看起来更像是一种“黑客行为”,而不是清晰的意图表达。
  3. 性能考量: 虽然
    key()
    函数本身是优化的,但在处理超大型文档时,反复调用
    key()
    可能会带来一定的性能开销。
  4. 不支持复杂分组条件: Muenchian Grouping主要适用于基于单一键值的分组。像
    group-starting-with
    那种基于节点位置和上下文的分组,用Muenchian Grouping实现起来会异常困难,甚至不可能。
  5. 嵌套分组的挑战: 虽然可以实现,但嵌套的Muenchian Grouping会使得表达式更加复杂,代码更难维护。

尽管有这些局限性,但对于那些仍然运行在XSLT 1.0环境下的系统来说,Muenchian Grouping依然是不可或缺的技能。它证明了即使在语言特性有限的情况下,开发者也能通过巧妙的组合实现复杂的功能。

除了基本分组,XSLT还能实现哪些复杂的节点分组场景?

XSLT的分组能力远不止是简单地按一个字段值来归类。在实际项目中,我遇到过各种稀奇古怪的分组需求,有些真的需要跳出常规思维去解决。这正是XSLT的魅力所在,它提供了一套工具集,让你能像搭积木一样,构建出满足特定业务逻辑的转换。

  1. 连续兄弟节点分组(

    group-starting-with
    的妙用) 这是我个人觉得
    for-each-group
    最出彩的地方之一,尤其是在处理半结构化文档(比如HTML)时。想象一下,你有一段HTML,里面有标题
    <h2>
    ,后面跟着几个段落
    <p>
    ,然后又是另一个
    <h2>
    。你希望把每个
    <h2>
    和它后面的所有
    <p>
    (直到下一个
    <h2>
    出现)作为一个组。

    XML输入 (模拟HTML片段):

    <document>
      <h2>Section A</h2>
      <p>Content for A, paragraph 1.</p>
      <p>Content for A, paragraph 2.</p>
      <h2>Section B</h2>
      <p>Content for B, paragraph 1.</p>
      <ul><li>List item 1</li><li>List item 2</li></ul>
      <p>Content for B, paragraph 2.</p>
      <h2>Section C</h2>
      <p>Content for C.</p>
    </document>

    XSLT (使用

    group-starting-with
    ):

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match="/document">
        <sections>
          <!-- 以h2节点作为新组的开始 -->
          <xsl:for-each-group select="*" group-starting-with="h2">
            <section title="{current-group()[1]}"> <!-- current-group()[1]是h2节点 -->
              <content>
                <xsl:copy-of select="current-group()[position() > 1]"/> <!-- 复制h2后面的所有内容 -->
              </content>
            </section>
          </xsl:for-each-group>
        </sections>
      </xsl:template>
    
    </xsl:stylesheet>

    这里,

    group-starting-with="h2"
    告诉XSLT,每当遇到一个
    <h2>
    节点,就开启一个新的组。这个组会包含
    <h2>
    本身,以及它后面所有的兄弟节点,直到遇到下一个
    <h2>
    为止。这对于将扁平的HTML结构转换为逻辑上的章节结构非常有效。

  2. 基于动态或计算值的分组 有时候,分组的依据不是一个简单的属性值,而是一个需要计算出来的结果。比如,我们想把商品按照价格区间(0-50, 51-100, 101-200等)来分组。

    XSLT (计算价格区间):

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match="/root">
        <price-ranges>
          <xsl:for-each-group select="item" group-by="floor(

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

203

2023.11.20

pdf怎么转换成xml格式
pdf怎么转换成xml格式

将 pdf 转换为 xml 的方法:1. 使用在线转换器;2. 使用桌面软件(如 adobe acrobat、itext);3. 使用命令行工具(如 pdftoxml)。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1950

2024.04.01

xml怎么变成word
xml怎么变成word

步骤:1. 导入 xml 文件;2. 选择 xml 结构;3. 映射 xml 元素到 word 元素;4. 生成 word 文档。提示:确保 xml 文件结构良好,并预览 word 文档以验证转换是否成功。想了解更多xml的相关内容,可以阅读本专题下面的文章。

2120

2024.08.01

xml是什么格式的文件
xml是什么格式的文件

xml是一种纯文本格式的文件。xml指的是可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。想了解更多相关的内容,可阅读本专题下面的相关文章。

1180

2024.11.28

Python WebSocket实时通信与异步服务开发实践
Python WebSocket实时通信与异步服务开发实践

本专题聚焦 Python 在实时通信场景中的开发实践,系统讲解 WebSocket 协议原理、长连接管理、消息推送机制以及异步服务架构设计。内容包括客户端与服务端通信实现、连接稳定性优化、消息队列集成及高并发处理策略。通过完整案例,帮助开发者构建高效稳定的实时通信系统,适用于聊天应用、实时数据推送等场景。

5

2026.03.18

Java Spring Security权限控制与认证机制实战
Java Spring Security权限控制与认证机制实战

本专题围绕 Java 后端安全体系建设展开,重点讲解 Spring Security 在权限控制与认证机制中的应用实践。内容涵盖用户认证流程、权限模型设计、JWT 鉴权方案、OAuth2 集成以及接口安全防护策略。通过实际项目案例,帮助开发者构建安全可靠的后端认证体系,提升系统安全性与可扩展能力。

21

2026.03.18

抖漫入口地址合集
抖漫入口地址合集

本专题整合了抖漫入口地址相关合集,阅读专题下面的文章了解更多详细地址。

138

2026.03.17

多环境下的 Nginx 安装、结构与运维实战
多环境下的 Nginx 安装、结构与运维实战

本专题聚焦多环境下Nginx实战,详解开发、测试及生产环境的差异化安装策略与目录结构规划。深入剖析配置模块化设计、灰度发布流程及跨环境同步机制。结合监控告警、故障排查与自动化运维工具,提供全链路管理方案,助力团队构建灵活、高可用的Nginx服务体系,从容应对复杂业务场景挑战。

14

2026.03.17

PS 批量添加图片
PS 批量添加图片

本专题整合了PS批量添加图片教程合集,阅读专题下面的文章了解更多详细操作。

15

2026.03.17

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6.2万人学习

ASP 教程
ASP 教程

共34课时 | 6.1万人学习

Vue3.x 工具篇--十天技能课堂
Vue3.x 工具篇--十天技能课堂

共26课时 | 1.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号