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!
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:
Thanks again to @Tomalak for doing the heavy lifting...
Transformations that should keep nearly all the input unchanged are best started with the identity template.
Then you override that template accordingly.
And then you're done.