如何用XSLT group-starting-with按Lv属性结构化扁平XML
用XSLT 2/3的
for-each-group实现按Lv属性嵌套扁平XML 核心思路
利用for-each-group的group-starting-with属性,以当前层级的起始元素作为分组标记,自动将后续属于该层级的子元素归入同一组,再递归处理组内的子层级元素。关键是通过Lv属性的大小关系判断元素的归属层级:后续元素的Lv必须大于当前组起始元素的Lv,且直到遇到第一个Lv≤当前起始元素Lv的元素为止,这些元素都属于当前组的子元素。
示例输入(扁平XML)
<root> <item pos="1" Lv="1" val="Rule A"/> <item pos="2" Lv="2" val="Subrule A1"/> <item pos="3" Lv="3" val="Sub-subrule A1a"/> <item pos="4" Lv="2" val="Subrule A2"/> <item pos="5" Lv="1" val="Rule B"/> <item pos="6" Lv="2" val="Subrule B1"/> </root>
XSLT 3实现代码
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <!-- 处理根节点,初始化顶层Lv=1的分组 --> <xsl:template match="root"> <structured-root> <xsl:for-each-group select="item" group-starting-with="item[@Lv = 1]"> <xsl:call-template name="nest-items"/> </xsl:for-each-group> </structured-root> </xsl:template> <!-- 递归嵌套模板:处理当前层级元素及其子元素 --> <xsl:template name="nest-items"> <!-- 生成当前层级的节点 --> <item level="{current-group()[1]/@Lv}" value="{current-group()[1]/@val}"> <!-- 筛选当前组中除起始元素外的所有候选子元素 --> <xsl:variable name="child-candidates" select="current-group()[position() > 1]"/> <!-- 对候选子元素按下一层级分组 --> <xsl:if test="$child-candidates"> <xsl:for-each-group select="$child-candidates" group-starting-with="item[@Lv = current-group()[1]/@Lv + 1]"> <xsl:call-template name="nest-items"/> </xsl:for-each-group> </xsl:if> </item> </xsl:template> </xsl:stylesheet>
关键逻辑说明
- 顶层分组:
group-starting-with="item[@Lv = 1]"会将所有item按Lv=1的元素拆分,每个组包含一个顶层规则及其所有下属子规则。 - 子层级分组:在递归模板中,用
group-starting-with="item[@Lv = current-group()[1]/@Lv + 1]"定位当前层级的直接子元素,自动将后续属于该子层级的元素归入同一组。 - 归属判断:
current-group()会自动包含从当前起始元素到下一个符合group-starting-with条件的元素之间的所有节点,无需手动判断后续兄弟的归属——XSLT会自动处理边界。
转换后输出(结构化XML)
<structured-root> <item level="1" value="Rule A"> <item level="2" value="Subrule A1"> <item level="3" value="Sub-subrule A1a"/> </item> <item level="2" value="Subrule A2"/> </item> <item level="1" value="Rule B"> <item level="2" value="Subrule B1"/> </item> </structured-root>
特殊情况适配
如果你的Lv属性不是连续递增(比如存在Lv=1直接跳到Lv=3的情况),可以修改group-starting-with的条件为:
group-starting-with="item[@Lv > current-group()[1]/@Lv and not(preceding-sibling::item[@Lv > current-group()[1]/@Lv and @Lv < current()/@Lv])]"
该条件会将当前起始元素之后、第一个Lv更大且没有中间层级的元素作为子组起始。
内容的提问来源于stack exchange,提问作者Binsky
相关产品推荐
相关产品推荐

