我正在尝试根据4维度排序xml文件 - 节点名称,属性名称,属性值,最后基于节点值。
我的XML
<NodeRoot>
<NodeA class="3">
<NodeB>
<NodeC abc="1">103</NodeC>
<NodeD>103</NodeD>
<NodeC pqr="2">101</NodeC>
<NodeC pqr="1">102</NodeC>
<NodeD>101</NodeD>
</NodeB>
</NodeA>
<NodeA class="1">
<NodeGroup>
<NodeC name="z" asc="2">103</NodeC>
<NodeC name="b">101</NodeC>
<NodeC name="a">102</NodeC>
</NodeGroup>
</NodeA>
</NodeRoot>
我的XSL
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<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="."/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
当前输出
<NodeRoot>
<NodeA class="1">
<NodeGroup>
<NodeC name="b">101</NodeC>
<NodeC name="a">102</NodeC>
<NodeC asc="2" name="z">103</NodeC>
</NodeGroup>
</NodeA>
<NodeA class="3">
<NodeB>
<NodeC pqr="2">101</NodeC>
<NodeC pqr="1">102</NodeC>
<NodeC abc="1">103</NodeC>
<NodeD>101</NodeD>
<NodeD>103</NodeD>
</NodeB>
</NodeA>
</NodeRoot>
预期结果
<NodeRoot>
<NodeA class="1">
<NodeGroup>
<NodeC asc="2" name="z">103</NodeC>
<NodeC name="a">102</NodeC>
<NodeC name="b">101</NodeC>
</NodeGroup>
</NodeA>
<NodeA class="3">
<NodeB>
<NodeC abc="1">103</NodeC>
<NodeC pqr="1">102</NodeC>
<NodeC pqr="2">101</NodeC>
<NodeD>101</NodeD>
<NodeD>103</NodeD>
</NodeB>
</NodeA>
</NodeRoot>
测试XSLT - &gt; http://xsltransform.net/naZXpY7
答案 0 :(得分:1)
您当前正在按本地名称和值对所有元素进行排序,然后是所有子项(再次按本地名称和字符串值)。
到目前为止,非常好。
您面临的一个难题是通过&#34;属性名称排序&#34;到底是什么意思。在您的示例中,看起来您希望元素按字母顺序按其属性名称列表排序,以便NodeGroup元素的子元素的排序键为
'NodeC', 'asc name', '2 z', 103
'NodeC', 'name', 'a', 102
'NodeC', 'name', 'b', 201
接下来的困难是没有明显的方法来获得价值&#39; asc name&#39;从XPath 1.0表达式开始,将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扩展用于用户定义的函数。
具有EXSLT功能的XSLT 1.0中的解决方案
如果您的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 -
这会产生你想要的输出。
答案 1 :(得分:0)
基于@C。 M. Sperberg-McQueen关于节点集扩展的建议,并在https://www.xml.com/pub/a/2003/07/16/nodeset.html的示例的帮助下,我想出了一个xsl合并了McQueen的2 xsls。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" exclude-result-prefixes="exslt" 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()|@*" name="first-pass">
<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:template match="/">
<xsl:variable name="process-one">
<xsl:call-template name="first-pass"/>
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($process-one)" mode="second-pass"/>
</xsl:template>
<xsl:template match="@*|node()" mode="second-pass">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="second-pass"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="second-pass">
<xsl:copy>
<xsl:apply-templates select="@*" mode="second-pass">
<xsl:sort select="local-name()"/>
<xsl:sort select="."/>
</xsl:apply-templates>
<xsl:apply-templates select="node()" mode="second-pass">
<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" mode="second-pass"/>
</xsl:stylesheet>