根据名称,属性名称,属性值和节点值对XML进行排序

时间:2017-11-13 12:53:30

标签: xml xslt xslt-1.0

我正在尝试根据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

2 个答案:

答案 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>