如何使用XSLT实现通用且结果一致的XML排序?
解决XSLT嵌套节点排序一致性问题
问题根源
你当前的XSLT中,<xsl:sort select="."/>是取节点的字符串值——对于包含子节点的元素(比如book),这个值是所有子节点文本的拼接结果。当输入里同类型节点的子节点顺序不一致时(比如有的book先写title,有的先写author),拼接后的字符串会变化,导致排序结果不稳定。
通用稳定排序方案
要实现无论输入顺序如何,输出排序始终一致的效果,需要给同类型节点生成不依赖输入顺序的固定排序依据。以下两种方案均适配任意层级嵌套和任意节点类型(如movie、product等):
方案1:基于标准化子节点的字符串键排序
通过递归模板生成节点的标准化字符串(子节点先按名称排序,再拼接文本),以此作为二次排序的依据:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:strip-space elements="*"/> <!-- 递归生成节点的稳定排序键:子节点按名称排序后拼接文本 --> <xsl:template name="get-stable-key"> <xsl:param name="node"/> <xsl:value-of select="normalize-space($node/text())"/> <xsl:for-each select="$node/*"> <xsl:sort select="name()"/> <xsl:call-template name="get-stable-key"> <xsl:with-param name="node" select="."/> </xsl:call-template> </xsl:for-each> </xsl:template> <xsl:template match="*"> <xsl:copy> <xsl:apply-templates> <!-- 第一优先级:节点名称排序 --> <xsl:sort select="name()"/> <!-- 第二优先级:使用稳定排序键 --> <xsl:sort> <xsl:call-template name="get-stable-key"> <xsl:with-param name="node" select="."/> </xsl:call-template> </xsl:sort> </xsl:apply-templates> </xsl:copy> </xsl:template> </xsl:stylesheet>
方案2:多层结构化排序(更高精度)
用分隔符区分节点名称与值,递归生成结构化的排序键,避免字符串拼接可能的混淆:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="*"> <xsl:copy> <xsl:apply-templates> <xsl:sort select="name()"/> <xsl:sort> <xsl:call-template name="generate-structured-key"> <xsl:with-param name="node" select="."/> </xsl:call-template> </xsl:sort> </xsl:apply-templates> </xsl:copy> </xsl:template> <xsl:template name="generate-structured-key"> <xsl:param name="node"/> <xsl:value-of select="normalize-space($node/text())"/> <xsl:for-each select="$node/*"> <xsl:sort select="name()"/> <xsl:text>|</xsl:text> <xsl:value-of select="name()"/> <xsl:text>=</xsl:text> <xsl:call-template name="generate-structured-key"> <xsl:with-param name="node" select="."/> </xsl:call-template> </xsl:for-each> </xsl:template> <xsl:template match="text() | @*"> <xsl:copy/> </xsl:template> </xsl:stylesheet>
效果验证
使用你的示例输入,无论book的子节点顺序如何,输出的book节点排序都会固定(比如始终是author=Dan的book在前,或者始终是author=John的在前),不会随输入顺序变化而波动。
内容的提问来源于stack exchange,提问作者OreganoGo
相关产品推荐
相关产品推荐

