请求递归XSLT实现扁平XML结构转多层级结构
问题:将扁平XML转换为嵌套层级结构,理解XSLT模板与for-each的区别
我有如下扁平结构的XML:
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <Report> <BCOT> <Detail_Collection> <Detail> <ElementId>Element1</ElementId> <ParentElementId>Element0</ParentElementId> </Detail> <Detail> <ElementId>Element2</ElementId> <ParentElementId>Element1</ParentElementId> </Detail> <Detail> <ElementId>Element3</ElementId> <ParentElementId>Element2</ParentElementId> </Detail> <Detail> <ElementId>Element4</ElementId> <ParentElementId>Element3</ParentElementId> </Detail> <Detail> <ElementId>Element5</ElementId> <ParentElementId>Element3</ParentElementId> </Detail> <Detail> <ElementId>Element6</ElementId> <ParentElementId>Element1</ParentElementId> </Detail> <Detail> <ElementId>Element7</ElementId> <ParentElementId>Element6</ParentElementId> </Detail> <Detail> <ElementId>Element8</ElementId> <ParentElementId>Element7</ParentElementId> </Detail> <Detail> <ElementId>Element9</ElementId> <ParentElementId>Element8</ParentElementId> </Detail> <Detail> <ElementId>Element10</ElementId> <ParentElementId>Element9</ParentElementId> </Detail> <Detail> <ElementId>Element11</ElementId> <ParentElementId>Element10</ParentElementId> </Detail> <Detail> <ElementId>Element12</ElementId> <ParentElementId>Element11</ParentElementId> </Detail> <Detail> <ElementId>Element13</ElementId> <ParentElementId>Element11</ParentElementId> </Detail> <Detail> <ElementId>Element14</ElementId> <ParentElementId>Element11</ParentElementId> </Detail> <Detail> <ElementId>Element15</ElementId> <ParentElementId>Element9</ParentElementId> </Detail> <Detail> <ElementId>Element16</ElementId> <ParentElementId>Element15</ParentElementId> </Detail> <Detail> <ElementId>Element17</ElementId> <ParentElementId>Element16</ParentElementId> </Detail> <Detail> <ElementId>Element18</ElementId> <ParentElementId>Element16</ParentElementId> </Detail> </Detail_Collection> </BCOT> </Report>
需要将其转换为如下多层级嵌套结构的XML:
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <Report> <BCOT> <Detail_Collection> <Detail> <ElementId>Element1</ElementId> <ParentElementId>Element0</ParentElementId> <Detail> <ElementId>Element2</ElementId> <ParentElementId>Element1</ParentElementId> <Detail> <ElementId>Element3</ElementId> <ParentElementId>Element2</ParentElementId> <Detail> <ElementId>Element4</ElementId> <ParentElementId>Element3</ParentElementId> </Detail> <Detail> <ElementId>Element5</ElementId> <ParentElementId>Element3</ParentElementId> </Detail> </Detail> </Detail> <Detail> <ElementId>Element6</ElementId> <ParentElementId>Element1</ParentElementId> <Detail> <ElementId>Element7</ElementId> <ParentElementId>Element6</ParentElementId> <Detail> <ElementId>Element8</ElementId> <ParentElementId>Element7</ParentElementId> <Detail> <ElementId>Element9</ElementId> <ParentElementId>Element8</ParentElementId> <Detail> <ElementId>Element10</ElementId> <ParentElementId>Element9</ParentElementId> <Detail> <ElementId>Element11</ElementId> <ParentElementId>Element10</ParentElementId> <Detail> <ElementId>Element12</ElementId> <ParentElementId>Element11</ParentElementId> </Detail> <Detail> <ElementId>Element13</ElementId> <ParentElementId>Element11</ParentElementId> </Detail> <Detail> <ElementId>Element14</ElementId> <ParentElementId>Element11</ParentElementId> </Detail> </Detail> </Detail> <Detail> <ElementId>Element15</ElementId> <ParentElementId>Element9</ParentElementId> <Detail> <ElementId>Element16</ElementId> <ParentElementId>Element15</ParentElementId> <Detail> <ElementId>Element17</ElementId> <ParentElementId>Element16</ParentElementId> </Detail> <Detail> <ElementId>Element18</ElementId> <ParentElementId>Element16</ParentElementId> </Detail> </Detail> </Detail> </Detail> </Detail> </Detail> </Detail> </Detail> </Detail_Collection> </BCOT> </Report>
我了解到递归任务更适合使用XSLT模板,但始终没能完全理解模板与for-each的区别。曾尝试固定次数循环的实现方式,但希望通过递归完成转换,能否提供帮助?
解答
先搞懂模板(xsl:template)和for-each的核心区别
for-each是迭代式处理:它会把你指定的节点集拉到同一个上下文里逐个处理,适合简单的循环场景,但处理嵌套树形结构时,你得手动维护层级关系,代码会变得非常冗余且难以扩展。- 模板是声明式+递归式处理:模板基于节点匹配规则触发,每个模板只负责处理特定类型的节点。当需要处理子节点时,只需要调用匹配子节点的模板,XSLT会自动切换上下文,完美适配递归场景,代码更简洁、易维护。
实现递归转换的XSLT代码
下面是针对你的需求写的XSLT,核心就是用模板递归匹配子节点:
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 复制所有未被匹配的节点(保留根结构、容器等) --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <!-- 清空默认的Detail_Collection处理,我们要重新生成嵌套结构 --> <xsl:template match="Detail_Collection"> <xsl:copy> <!-- 找到所有父节点是Element0的Detail,作为根节点开始递归 --> <xsl:apply-templates select="//Detail[ParentElementId='Element0']"/> </xsl:copy> </xsl:template> <!-- 核心递归模板:处理单个Detail节点 --> <xsl:template match="Detail"> <xsl:copy> <!-- 复制当前节点的子元素(ElementId和ParentElementId) --> <xsl:apply-templates select="ElementId|ParentElementId"/> <!-- 递归查找所有以当前ElementId为父节点的Detail子节点,调用同样的模板 --> <xsl:apply-templates select="//Detail[ParentElementId=current()/ElementId]"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
代码逻辑解释
- 默认复制模板:第一个模板会复制所有未被专门匹配的节点,保证
Report、BCOT这些上层结构原样输出。 - 重写Detail_Collection处理:清空原来的扁平Detail列表,只筛选出父节点是
Element0的Detail作为嵌套结构的根节点,触发递归处理。 - 递归Detail模板:每个Detail节点先复制自己的
ElementId和ParentElementId,然后自动查找所有以自己为父节点的子Detail,对这些子节点再次应用本模板,形成嵌套层级。
这样运行这个XSLT就能把你的扁平XML转换成目标的嵌套结构啦。
内容的提问来源于stack exchange,提问作者Martin Purdy
相关产品推荐
相关产品推荐

