更改XML元素排序,同时保持结构层次结构和属性

时间:2013-10-11 11:19:44

标签: xml xslt

我希望改变一些XML元素的顺序。 XML很复杂并且由一个单独的进程生成 - 我不需要更改它,所以我希望使用XSLT来纠正元素顺序。

我不是XSLT专家(!)所以我寻找了一些片段并找到了一些东西,只需稍加修改以适应我的情况,几乎就可以了。我目前拥有的最佳版本以正确的顺序输出元素,但删除了所有属性。

我创建了一个更简单的xml和相应的xsl以及我的问题的相关功能。

这是(虚拟)示例xml:

<?xml version="1.0" encoding="UTF-8"?>
<Companies xmlns="company:fruit:ns" Version="1.0">
  <Description>Some example companies and fruit shipments</Description>
  <Company CompanyId="Acme">
    <Description>Some example shipments</Description>
    <Shipment Id="ABC">
      <Description>Some apples</Description>
      <Fruit>
        <Apples>10</Apples>
      </Fruit>
    </Shipment>
    <Shipment Id="DEF">
      <Description>Some oranges and pears</Description>
      <Fruit>
        <Oranges>20</Oranges>
        <Pears>20</Pears>
      </Fruit>
    </Shipment>
    <Shipment Id="JKL">
      <Description>Empty</Description>
      <Fruit/>
    </Shipment>
    <Fruit/>
  </Company>
  <Fruit/>
</Companies>

问题是在Company-Description元素后面应该有一个Company-Fruit元素(而不是它跟在所有Shipment元素之后),并且在Companies-Description元素后面应该有一个Companies-Fruit元素(相反,它遵循所有公司 - 公司要素)。我使用以下xsl转换来纠正元素排序:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="company:fruit:ns">
  <!-- See http://xsltbyexample.blogspot.com/2008/02/re-arrange-order-of-elements-in-xml.html -->
  <xsl:output omit-xml-declaration="no" indent="yes" method="xml" encoding="utf-8"/>
  <xsl:strip-space elements="*"/>
  <xsl:template match="*">
    <xsl:apply-templates select="self::*" mode="copy"/>
  </xsl:template>
  <xsl:template match="Company/Description">
    <xsl:message>Matched Company Description</xsl:message>
    <xsl:apply-templates select="self::*" mode="copy"/>
    <xsl:apply-templates select="../Fruit" mode="copy"/>
  </xsl:template>
  <xsl:template match="Companies/Description">
    <xsl:message>Matched Companies Description</xsl:message>
    <xsl:apply-templates select="self::*" mode="copy"/>
    <xsl:apply-templates select="../Fruit" mode="copy"/>
  </xsl:template>
  <xsl:template match="Company/Fruit"/>
  <xsl:template match="Companies/Fruit"/>
  <xsl:template match="*" mode="copy">
    <xsl:copy>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="text()">
    <xsl:value-of select="."/>
  </xsl:template>
</xsl:stylesheet>

生成的xml具有正确的排序,但大部分属性已被删除:

<?xml version="1.0" encoding="utf-8"?>
<Companies xmlns="company:fruit:ns">
   <Description>Some example companies and fruit shipments</Description>
   <Fruit/>
   <Company>
      <Description>Some example shipments</Description>
      <Fruit/>
      <Shipment>
         <Description>Some apples</Description>
         <Fruit>
            <Apples>10</Apples>
         </Fruit>
      </Shipment>
      <Shipment>
         <Description>Some oranges and pears</Description>
         <Fruit>
            <Apples>20</Apples>
            <Pears>20</Pears>
         </Fruit>
      </Shipment>
      <Shipment>
         <Description>Empty</Description>
         <Fruit/>
      </Shipment>
   </Company>
</Companies>

我欢迎来自XSLT专家的任何建议!

2 个答案:

答案 0 :(得分:4)

应该保持几乎所有输入不变的转换最好用身份模板开始。

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

然后相应地覆盖该模板。

<!-- throw away <Fruit> elements, initially - they are handled separately -->
<xsl:template match="Company/Fruit | Companies/Fruit" />

<!-- re-build <Company> and <Companies> in the correct order -->
<xsl:template match="Company | Companies">
  <xsl:copy>
    <xsl:copy-of select="@*" />
    <xsl:copy-of select="Fruit" />
    <xsl:apply-templates select="node()" />
  </xsl:copy>
</xsl:template>

然后你就完成了。

答案 1 :(得分:3)

@Tomalak的解决方案显示了如何重新排序元素,例如<Fruit>下的<Company>元素不再跟随<Shipment>元素。但是@ Tomalak的解决方案将<Fruit>元素放在<Description>元素之前。排序应为<Description><Fruit><Shipment>,...

因此,为了完整起见,并证明我已经从@ Tomalak的解决方案(!)中学习,xsl完整版的相关部分如下所示:

<!-- throw away <Fruit> and <Description> elements, initially - they are handled separately -->
<xsl:template match="Company/Fruit | Companies/Fruit | Companies/Description | Company/Description"/>

<!-- re-build <Company> and <Companies> in the correct order -->
<xsl:template match="Company | Companies">
<xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:copy-of select="Description"/>
    <xsl:copy-of select="Fruit"/>
    <xsl:apply-templates select="node()"/>
  </xsl:copy>
</xsl:template>

再次感谢@Tomalak做重任......