xslt 2.0:创建仅包含每个节点一次的层次结构

时间:2016-01-08 11:01:07

标签: xml xslt xslt-2.0 saxon

我不知道这是不是最好的标题,但这就是我想做的事情:

我的输入文件:

 <?xml version="1.0" encoding="UTF-8"?>
    <out>
    <cat id="d1e3">
        <ip level="1" id="d1e3a1814" content="ABC">
            <ip level="2" id="d1e3a1815" content="DEF"/>
        </ip>
        <pq level="1" id="d1e3a1911" content="XPQ"/>
    </cat>
    <cat id="d1e8">
        <ip level="1" id="d1e8a1814" content="ABC">
            <ip level="2" id="d1e8a1815" content="TXTXT"/>
        </ip>
        <pq level="1" id="d1e8a1911" content="XPQ"/>
    </cat>
    <cat id="d1e13">
        <ip level="1" id="d1e13a1814" content="ABC">
            <ip level="2" id="d1e13a1815" content="TXTXT"/>
        </ip>
        <pq level="1" id="d1e13a1911" content="XPQ">
            <pq level="2" id="d1e13a1912" content="1234"/>
        </pq>
    </cat>
    <cat id="d1e569">
        <ip level="1" id="d1e569a1814" content="ABC">
            <ip level="2" id="d1e569a1815" content="TXTXT"/>
        </ip>
        <pq level="1" id="d1e569a1911" content="XPQ">
            <pq level="2" id="d1e569a1912" content="1234">
                <pq level="3" id="d1e569a1913" content="345">
                    <pq level="4" id="d1e569a1914" content="456">
                        <pq level="5" id="d1e569a1915" content="567"/>
                    </pq>
                </pq>
            </pq>
        </pq>
    </cat>
    <cat id="d1e666">
        <ip level="1" id="d1e666a1814" content="ABC">
            <ip level="2" id="d1e666a1815" content="TXTXT"/>
        </ip>
        <pq level="1" id="d1e666a1911" content="XPQ">
            <pq level="2" id="d1e666a1912" content="1234">
                <pq level="3" id="d1e666a1913" content="8787"/>
            </pq>
        </pq>
    </cat>
    </out>

我想要的输出:

 <?xml version="1.0" encoding="UTF-8"?>
    <out>
        <new level="1" id="d1e3a1814" content="ABC">
            <new level="2" id="d1e3a1815" content="DEF"/>
            <new level="2" id="d1e8a1815" content="TXTXT"/>
        </new>
         <new level="1" id="d1e13a1911" content="XPQ">
            <new level="2" id="d1e569a1912" content="1234">
                <new level="3" id="d1e569a1913" content="345">
                    <new level="4" id="d1e569a1914" content="456">
                        <new level="5" id="d1e569a1915" content="567"/>
                    </new>
                </new>
            </new>
            <new level="3" id="d1e666a1913" content="8787"/>
        </new>
    </out>

使用xslt 2.0和saxon9he。

所以这里发生的事情是每个ip节点在输出文档中只出现一次,并且每个子节点也只出现一次,同样适用于那些chuildren的子节点,依此类推。这同样适用于pq节点。

每个节点只应出现一次,同时保持层次结构。基本上,我需要一个文件,我可以告诉它,哪个节点是其他节点的父节点,但我只需要为每个节点提供一次这样的信息(因此实质上,每个id应该只在输出文档中出现一次)。

在输入文档中,ippq可以包含任意数量的子节点,并且它们可以位于每个node元素中。

我尝试在按ip分组的所有@id元素上使用for-each-group,但我无法弄清楚如何从那里开始。我需要递归地执行此操作吗?

我会感谢您的提示和帮助!

1 个答案:

答案 0 :(得分:1)

运行时

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:key name="ip-id" match="ip" use="@id"/>
    <xsl:key name="pq-id" match="pq" use="@id"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cat">
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="ip[. is key('ip-id', @id)[1]]">
        <new-ip>
            <xsl:apply-templates select="@* , key('ip-id', @id)/ip"/>
        </new-ip>
    </xsl:template>

    <xsl:template match="ip[not(. is key('ip-id', @id)[1])]"/>

    <xsl:template match="pq[. is key('pq-id', @id)[1]]">
        <new-pq>
            <xsl:apply-templates select="@* , key('pq-id', @id)/pq"/>
        </new-pq>
    </xsl:template>

    <xsl:template match="pq[not(. is key('pq-id', @id)[1])]"/>

</xsl:transform>

反对你的输入,我得到了结果

<out>
   <new-ip level="1" id="d1e3a1814" content="ABC">
      <new-ip level="2" id="d1e3a1815" content="DEF"/>
   </new-ip>
   <new-pq level="1" id="d1e3a1911" content="XPQ"/>
   <new-ip level="1" id="d1e8a1814" content="ABC">
      <new-ip level="2" id="d1e8a1815" content="TXTXT"/>
   </new-ip>
   <new-pq level="1" id="d1e8a1911" content="XPQ"/>
   <new-ip level="1" id="d1e13a1814" content="ABC">
      <new-ip level="2" id="d1e13a1815" content="TXTXT"/>
   </new-ip>
   <new-pq level="1" id="d1e13a1911" content="XPQ">
      <new-pq level="2" id="d1e13a1912" content="1234"/>
   </new-pq>
   <new-ip level="1" id="d1e569a1814" content="ABC">
      <new-ip level="2" id="d1e569a1815" content="TXTXT"/>
   </new-ip>
   <new-pq level="1" id="d1e569a1911" content="XPQ">
      <new-pq level="2" id="d1e569a1912" content="1234">
         <new-pq level="3" id="d1e569a1913" content="345">
            <new-pq level="4" id="d1e569a1914" content="456">
               <new-pq level="5" id="d1e569a1915" content="567"/>
            </new-pq>
         </new-pq>
      </new-pq>
   </new-pq>
   <new-ip level="1" id="d1e666a1814" content="ABC">
      <new-ip level="2" id="d1e666a1815" content="TXTXT"/>
   </new-ip>
   <new-pq level="1" id="d1e666a1911" content="XPQ">
      <new-pq level="2" id="d1e666a1912" content="1234">
         <new-pq level="3" id="d1e666a1913" content="8787"/>
      </new-pq>
   </new-pq>
</out>

我意识到在你想要的结果中有更多的节点,但我认为他们的id属性是唯一的。

使用content属性而非id属性标识ippq元素时,会创建您根据需要发布的输出:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:key name="content" match="ip | pq" use="@content"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cat">
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="ip[. is key('content', @content)[1]] | pq[. is key('content', @content)[1]]">
        <new>
            <xsl:apply-templates select="@* , key('content', @content)/*"/>
        </new>
    </xsl:template>

    <xsl:template match="ip[not(. is key('content', @content)[1])] | pq[not(. is key('content', @content)[1])]"/>

</xsl:transform>