Change XML element ordering while keeping structur

2019-05-17 08:56发布

I wish to change the order of some XML elements. The XML is complex and generated by a separate process - I am not fee to change it, so I was hoping to use XSLT to correct the element order.

I am not an XSLT expert(!) so I looked for some snippets and found something that, with minor changes to suit my case, almost works. The best version I have at present outputs elements in the correct order, but strips out all the attributes.

I created a simpler xml and corresponding xsl with the relevant features of my problem.

Here is the (dummy) example 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>

The problem is that there should be a Company-Fruit element following the Company-Description element (instead it follows all the Shipment elements) and there should be a Companies-Fruit element following the Companies-Description element (instead it follows all the Companies-company elements). I used the following xsl transformation to correct the element ordering:

<?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>

The resulting xml has the right ordering but most of the attributes have been stripped out:

<?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>

I would welcome any advice from the XSLT experts out there!

标签: xml xslt
2条回答
兄弟一词,经得起流年.
2楼-- · 2019-05-17 09:33

The solution by @Tomalak shows how to reorder elements such that, say, the <Fruit> elements under <Company> no longer follow the <Shipment> elements. However @Tomalak's solution puts the <Fruit> elements before the <Description> elements. The ordering should be <Description>,<Fruit>,<Shipment>,....

Therefore for the sake of completeness, and to prove that I have learnt from @Tomalak's solution(!), the relevant parts of the complete version of the xsl are shown here:

<!-- 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>

Thanks again to @Tomalak for doing the heavy lifting...

查看更多
我只想做你的唯一
3楼-- · 2019-05-17 09:38

Transformations that should keep nearly all the input unchanged are best started with the identity template.

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

Then you override that template accordingly.

<!-- 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>

And then you're done.

查看更多
登录 后发表回答