如何用XSLT 2.0/3.0将相邻XML元素分组为嵌套有序列表
问题描述
手里有一份层级结构混乱的XML,要把它转成多级嵌套的有序列表,要求如下:
- 只处理
name属性是level_1到level_6的<p>元素,按层级嵌套 name为level_0的元素,以及<note>这类非目标元素直接忽略- 转换逻辑要通用,能适配level_1到level_6的任意层级嵌套
源XML:
<root> <p name="level_0">Value excluded from grouping.</p> <p name="level_1">Value 1</p> <p name="level_1">Value 2</p> <p name="level_2">Value 3</p> <p name="level_2">Value 4</p> <p name="level_1">Value 5</p> <note>Some text</note> <p name="level_1">Another Value 1</p> <p name="level_2">Another Value 2</p> <p name="level_2">Another Value 3</p> <p name="level_3">Another Value 4</p> <p name="level_3">Another Value 5</p> <p name="level_4">Another Value 3</p> <note>Some text</note> <p name="level_4">Another Value 4</p> <p name="level_1">Another Value 5</p> <p name="level_0">Value excluded from grouping.</p> </root>
期望输出:
<ol class="ol-level_1"> <li class="li-level_1">Value 1</li> <li class="li-level_1"> Value 2 <ol class="ol-level_2"> <li class="li-level_2">Value 3</li> <li class="li-level_2">Value 4</li> </ol> </li> <li class="li-level_1">Value 5</li> <li class="li-level_1"> Another Value 1 <ol class="ol-level_2"> <li class="li-level_2">Another Value 2</li> <li class="li-level_2"> Another Value 3 <ol class="ol-level_3"> <li class="li-level_3">Another Value 4</li> <li class="li-level_3"> Another Value 5 <ol class="ol-level_4"> <li class="li-level_4">Another Value 3</li> <li class="li-level_4">Another Value 4</li> </ol> </li> </ol> </li> </ol> </li> <li class="li-level_1">Another Value 5</li> </ol>
你尝试的XSLT代码(不符合预期):
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math" version="2.0"> <xsl:output method="xml" indent="yes"/> <xsl:template match="root"> <ol class="ol-{@name}"> <xsl:for-each-group select="*" group-adjacent="@name"> <li class="li-{@name}"> <xsl:value-of select="current-grouping-key()"/> <ol class="ol-{@name}"> <xsl:for-each select="current-group()"> <li class="li-{@name}"> <xsl:value-of select="."/> </li> </xsl:for-each> </ol> </li> </xsl:for-each-group> </ol> </xsl:template> </xsl:stylesheet>
解决方案
问题分析
你之前的代码主要问题:
- 错误使用
group-adjacent="@name",同层级元素可能不相邻,且未处理层级嵌套逻辑 - 错误引用
root的@name属性(root根本没有这个属性) - 未过滤
level_0和非目标元素 - 仅做简单分组,未实现“子层级嵌套到父层级li内”的核心需求
正确的XSLT代码(通用适配level_1到level_6)
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:output method="xml" indent="yes"/> <!-- 忽略不需要处理的元素 --> <xsl:template match="p[@name='level_0'] | *[not(self::p) or not(starts-with(@name, 'level_'))]"/> <!-- 处理root,触发level_1元素的分组逻辑 --> <xsl:template match="root"> <xsl:apply-templates select="p[@name='level_1']" mode="group"/> </xsl:template> <!-- 递归处理层级分组的核心模板 --> <xsl:template match="p" mode="group"> <xsl:param name="current-level" select="number(substring-after(@name, 'level_'))"/> <li class="li-{@name}"> <xsl:value-of select="."/> <!-- 查找当前元素所属的子层级元素 --> <xsl:variable name="children" select="following-sibling::p[ number(substring-after(@name, 'level_')) = $current-level + 1 and not(following-sibling::p[number(substring-after(@name, 'level_')) <= $current-level][1] << .) ]"/> <xsl:if test="$children"> <ol class="ol-{substring-after(@name, 'level_') + 1}"> <xsl:apply-templates select="$children" mode="group"/> </ol> </xsl:if> </li> <!-- 处理当前元素之后的同层级下一个元素 --> <xsl:apply-templates select="following-sibling::p[ number(substring-after(@name, 'level_')) = $current-level and not(preceding-sibling::p[number(substring-after(@name, 'level_')) < $current-level][1] << .) ][1]" mode="group"/> </xsl:template> <!-- 给最外层level_1元素包裹ol --> <xsl:template match="p[@name='level_1'][1]" mode="group"> <ol class="ol-level_1"> <xsl:next-match/> </ol> </xsl:template> </xsl:stylesheet>
核心逻辑说明
- 过滤规则:通过模板匹配直接忽略
level_0和非目标元素,只保留level_1到level_6的<p> - 递归层级处理:用
mode="group"的模板递归遍历每个层级,通过substring-after提取层级数字,动态判断子层级关系 - 子元素范围判定:通过
following-sibling和位置逻辑,精准定位当前元素所属的子层级元素(即当前元素之后、下一个同层级或更高层级元素之前的所有子层级元素) - 通用适配:无需硬编码每个level的规则,通过动态计算层级数字,自动适配level_1到level_6的嵌套结构
内容的提问来源于stack exchange,提问作者EBamba
相关产品推荐
相关产品推荐

