XSLT问题:基于目录元数据的递归模板未达预期效果
基于目录元数据生成层级唯一标识符的XSLT解决方案
我正尝试从目录元数据生成唯一标识符,用于后续文本数据库的使用。我编写了一个使用<xsl:for-each>的递归调用模板,但未得到预期结果。
源文档
<html lang="en"> <head> <title>Title</title> </head> <body> <h1 data-toc="1" data-stub="Title1">Title 1—General</h1> <h2 data-toc="5" data-stub="Part1">PART 1—DEFINITIONS</h2> <h3 data-toc="9" data-stub="Sec1">§ 1 Definitions.</h3> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec1.1">§ 1.1 Foo.</h3> <p>foo</p> <p>foo</p> <h2 data-toc="5" data-stub="Part2">PART 2—THE COMMITTEE</h2> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec2">§ 2 Definitions.</h3> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec2.1">§ 2.1 Foo.</h3> <p>foo</p> <p>foo</p> <h2 data-toc="5" data-stub="Part3">PART 3—THE COUNCIL</h2> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec3">§ 3 Definitions.</h3> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec3.1">§ 3.1 Foo.</h3> <p>foo</p> <p>foo</p> </body> </html>
现有样式表
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0"> <xsl:output method="xhtml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> <xsl:template match="*[@data-toc]"> <xsl:variable name="build-callcode"> <xsl:call-template name="build-call-code"> <xsl:with-param name="this-toc-level" select="@data-toc"/> <xsl:with-param name="this-stub" select="@data-stub"/> </xsl:call-template> </xsl:variable> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:attribute name="data-callcode" select="$build-callcode"/> <xsl:apply-templates/> </xsl:copy> </xsl:template> <xsl:template name="build-call-code"> <xsl:param name="this-toc-level"/> <xsl:param name="this-stub"/> <xsl:for-each select="preceding::*[@data-toc < $this-toc-level][1]"> <xsl:call-template name="build-call-code"> <xsl:with-param name="this-toc-level" select="@data-toc"/> <xsl:with-param name="this-stub" select="concat(@data-stub, $this-stub)"/> </xsl:call-template> <xsl:value-of select="concat(@data-stub, $this-stub)"/> </xsl:for-each> </xsl:template> </xsl:stylesheet>
期望结果
<html lang="en"> <head> <title>Title</title> </head> <body> <h1 data-toc="1" data-stub="Title1" data-callcode="Title1">Title 1—General</h1> <h2 data-toc="5" data-stub="Part1" data-callcode="Title1Part1">PART 1—DEFINITIONS</h2> <h3 data-toc="9" data-stub="Sec1" data-callcode="Title1Part1Sec1">§ 1 Definitions.</h3> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec1.1" data-callcode="Title1Part1Sec1.1">§ 1.1 Foo.</h3> <p>foo</p> <p>foo</p> <h2 data-toc="5" data-stub="Part2" data-callcode="Title1Part2">PART 2—THE COMMITTEE</h2> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec2" data-callcode="Title1Part2Sec2">§ 2 Definitions.</h3> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec2.1" data-callcode="Title1Part2Sec2.1">§ 2.1 Foo.</h3> <p>foo</p> <p>foo</p> <h2 data-toc="5" data-stub="Part3" data-callcode="Title1Part3">PART 3—THE COUNCIL</h2> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec3" data-callcode="Title1Part3Sec3">§ 3 Definitions.</h3> <p>foo</p> <p>foo</p> <h3 data-toc="9" data-stub="Sec3.1" data-callcode="Title1Part3Sec3.1">§ 3.1 Foo.</h3> <p>foo</p> <p>foo</p> </body> </html>
问题分析与修正方案
原递归模板的核心问题:
- 递归时提前拼接父节点与当前节点的stub,导致层级逻辑混乱
- 仅获取最近的单个父层级节点,未正确追溯所有上层级的祖先节点
修正后的样式表
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0"> <xsl:output method="xhtml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> <xsl:template match="*[@data-toc]"> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:attribute name="data-callcode"> <xsl:call-template name="build-call-code"> <xsl:with-param name="current-node" select="."/> </xsl:call-template> </xsl:attribute> <xsl:apply-templates/> </xsl:copy> </xsl:template> <xsl:template name="build-call-code"> <xsl:param name="current-node"/> <!-- 获取当前节点之前最近的、层级更低的节点 --> <xsl:variable name="parent-node" select="$current-node/preceding::*[@data-toc < $current-node/@data-toc][1]"/> <!-- 递归输出所有父层级节点的stub --> <xsl:if test="$parent-node"> <xsl:call-template name="build-call-code"> <xsl:with-param name="current-node" select="$parent-node"/> </xsl:call-template> </xsl:if> <!-- 输出当前节点的stub --> <xsl:value-of select="$current-node/@data-stub"/> </xsl:template> </xsl:stylesheet>
修正逻辑说明
- 递归模板传递完整节点对象,避免拆分层级与stub导致的拼接错误
- 先递归追溯所有上层级的祖先节点,按层级顺序输出父节点的stub,最后输出当前节点的stub
- 顶层节点无父层级时,直接输出自身stub,符合预期的层级拼接规则
内容的提问来源于stack exchange,提问作者Paulb
相关产品推荐
相关产品推荐

