您目前正在按本地名称和值对元素的所有属性进行排序,然后是所有子项(再次按本地名称和字符串值)。
到目前为止,一切都很好。
您面临的一个困难是按“属性名称”排序的确切含义。从您的示例中,您似乎希望元素按字母顺序按属性名称列表排序,以便 NodeGroup 元素的子元素的排序键是
'NodeC', 'asc name', '2 z', 103
'NodeC', 'name', 'a', 102
'NodeC', 'name', 'b', 201
下一个困难是没有明显的方法可以从 XPath 1.0 表达式中获取值“asc name”,其中 NodeGroup 元素的第一个 NodeC 作为上下文节点。当然,可以生成字符串,但它需要调用命名模板。 (或者,更准确地说:如果没有这样的调用,我不知道如何生成它。)
XSLT 2.0 解决方案
XSLT 2.0 中的问题相对简单;以下片段显示了关键部分:
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="."/>
</xsl:apply-templates>
<xsl:apply-templates select="node()">
<xsl:sort select="local-name()"/>
<xsl:sort select="string-join(local:key2(.), ' ')"/>
<xsl:sort select="string-join(local:key3(.), ' ')"/>
<xsl:sort select="." data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:function name="local:key2" as="xs:string*">
<xsl:param name="e" as="node()"/>
<xsl:for-each select="$e/@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="string()"/>
<xsl:value-of select="local-name()"/>
</xsl:for-each>
</xsl:function>
<xsl:function name="local:key3" as="xs:string*">
<xsl:param name="e" as="node()"/>
<xsl:for-each select="$e/@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="string()"/>
<xsl:value-of select="string()"/>
</xsl:for-each>
</xsl:function>
这种通用方法也可以在 XSLT 1.0 中用于用户定义函数的 EXSLT 扩展。
XSLT 1.0 中带有 EXSLT 函数的解决方案
如果您的 XSLT 1.0 处理器支持 EXSLT 样式的用户定义函数,您可以在 XSLT 1.0 中执行类似的操作。 (我最初的尝试失败了,但是当我记得将extension-element-prefixes 属性添加到样式表元素时,错误就消失了。)
<xsl:stylesheet version="1.0"
extension-element-prefixes="func"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:func="http://exslt.org/functions"
xmlns:local="http://example.com/nss/dummy">
<xsl:output encoding="utf-8"
method="xml"
omit-xml-declaration="yes"
indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="."/>
</xsl:apply-templates>
<xsl:apply-templates select="node()">
<xsl:sort select="local-name()"/>
<xsl:sort select="local:key2(.)"/>
<xsl:sort select="local:key3(.)"/>
<xsl:sort select="." data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<func:function name="local:key2">
<xsl:param name="e" select="."/>
<func:result>
<xsl:for-each select="$e/@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="string()"/>
<xsl:value-of select="concat(local-name(), ' ')"/>
</xsl:for-each>
</func:result>
</func:function>
<func:function name="local:key3">
<xsl:param name="e" select="."/>
<func:result>
<xsl:for-each select="$e/@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="string()"/>
<xsl:value-of select="concat(string(), ' ')"/>
</xsl:for-each>
</func:result>
</func:function>
</xsl:stylesheet>
当使用 xsltproc 在您的输入上运行时,这会产生所需的输出。
您还可以使用节点集扩展在 XSLT 1.0 中做一些聪明的事情。
未扩展的 XSLT 1.0 中的两阶段管道
但我认为在未扩展的 XSLT 1.0 中解决此问题的最简单方法是将两个样式表连接在一起。第一个为每个元素添加两个属性,以提供排序键 2 和 3。(调整命名模板以使它们执行您想要的操作。)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:PJ="http://example.com/PankajJaju">
<xsl:output encoding="utf-8"
method="xml"
omit-xml-declaration="yes"
indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:if test="self::*">
<xsl:attribute name="PJ:attribute-names"
namespace="http://example.com/PankajJaju">
<xsl:call-template name="attribute-name-list"/>
</xsl:attribute>
<xsl:attribute name="PJ:attribute-values"
namespace="http://example.com/PankajJaju">
<xsl:call-template name="attribute-value-list"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template name="attribute-name-list">
<xsl:for-each select="@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="string()"/>
<xsl:value-of select="concat(local-name(), ' ')"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="attribute-value-list">
<xsl:for-each select="@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="string()"/>
<xsl:value-of select="concat(string(), ' ')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
我已将它们放入命名空间以减少名称冲突的可能性。
第二个使用排序键执行实际排序并抑制临时属性。
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:PJ="http://example.com/PankajJaju">
<xsl:output encoding="utf-8"
method="xml"
omit-xml-declaration="yes"
indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*">
<xsl:sort select="local-name()"/>
<xsl:sort select="."/>
</xsl:apply-templates>
<xsl:apply-templates select="node()">
<xsl:sort select="local-name()"/>
<xsl:sort select="@PJ:attribute-names"/>
<xsl:sort select="@PJ:attribute-values"/>
<xsl:sort select="."/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="@PJ:attribute-names | @PJ:attribute-values"/>
</xsl:stylesheet>
可以使用您喜欢的任何技术将这些流水线连接在一起。例如,从 bash 命令行使用 xsltproc,并将名称 p1.xsl 和 p2.xsl 分配给管道样式表 1 和 2 ...
xsltproc p1.xsl input.xml | xsltproc p2.xsl -
这会产生你想要的输出。