Reorder xml nodes, including sub-nodes, using xslt

I have an xml document, now I want to translate it into another XML document with the same content, but with different orders of elements.

Original XML document like:

<?xml version = "1.0" encoding = "UTF-8"?> <order xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" > <ship> <zipcode>78712</zipcode> <street>1234 Main Street</street> <country>CN</country> <city>Beijing</city> </ship> <items> <quantity>1</quantity> <itemno>1234</itemno> </items> <items> <quantity>3</quantity> <itemno>1235</itemno> </items> <price>456</price> <customer>Tom Hill</customer> </order> 

The expected XML output, for example:

 <?xml version = "1.0" encoding = "UTF-8"?> <order xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" > <customer>Tom Hill</customer> <ship> <street>1234 Main Street</street> <city>Beijing</city> <zipcode>78712</zipcode> <country>CN</country> </ship> <items> <itemno>1234</itemno> <quantity>1</quantity> </items> <items> <itemno>1235</itemno> <quantity>3</quantity> </items> <price>456</price> </order> 

I used the following xslt document to translate it.

 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/order"> <xsl:copy> <xsl:copy-of select="customer" /> <xsl:copy-of select="ship" > <xsl:call-template name="TShip" /> </xsl:copy-of> <xsl:copy-of select="items"> <xsl:call-template name="TItems" /> </xsl:copy-of> <xsl:copy-of select="price" /> </xsl:copy> </xsl:template> <xsl:template name="TShip"> <xsl:copy> <xsl:copy-of select="street" /> <xsl:copy-of select="city" /> <xsl:copy-of select="zipcode" /> <xsl:copy-of select="country" /> </xsl:copy> </xsl:template> <xsl:template name="TItems"> <xsl:for-each select="items"> <xsl:copy> <xsl:copy-of select="itemno" /> <xsl:copy-of select="quantity" /> </xsl:copy> </xsl:for-each> </xsl:template> </xsl:stylesheet> 

However, the translation result is not my expected one. Translated xml result:

 <?xml version = "1.0" encoding = "UTF-8"?> <order xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" > <customer>Tom Hill</customer> <ship> <zipcode>78712</zipcode> <street>1234 Main Street</street> <country>CN</country> <city>Beijing</city> </ship> <items> <quantity>1</quantity> <itemno>1234</itemno> </items> <items> <quantity>3</quantity> <itemno>1235</itemno> </items> <price>456</price> </order> 

He just made the first level nodes in the expected order. All auxiliary nodes are stored in the original order. How can I make the order of all nodes pending?

+4
source share
2 answers

xsl:copy-of also copies all child nodes, and child nodes are not evaluated.

Thus, your TShip and TItems templates are never evaluated. <xsl:copy-of select="ship"> copies all <ship>...</ship> .

This modification of your template will show that your TShip and TItems templates are not being called.

 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/order"> <xsl:copy> <xsl:copy-of select="customer" /> <xsl:copy-of select="ship"> <xsl:call-template name="TShip" /> </xsl:copy-of> <xsl:copy-of select="items"> <xsl:call-template name="TItems" /> </xsl:copy-of> <xsl:copy-of select="price" /> </xsl:copy> </xsl:template> <xsl:template name="TShip"> <xsl:copy> <test>TShip called</test> <xsl:copy-of select="street" /> <xsl:copy-of select="city" /> <xsl:copy-of select="zipcode" /> <xsl:copy-of select="country" /> </xsl:copy> </xsl:template> <xsl:template name="TItems"> <xsl:for-each select="items"> <xsl:copy> <test>TItems called</test> <xsl:copy-of select="itemno" /> <xsl:copy-of select="quantity" /> </xsl:copy> </xsl:for-each> </xsl:template> </xsl:stylesheet> 

Note that the output does not contain the <test> elements that I added.

What you need to do instead is recursive implicit copying. Usually xsl:copy , xsl:copy-of and xsl:for-each are a sign of poor design of the xsl template - there are very few problems that xsl:template and xsl:apply-template with identity conversion do not handle better.

Here's how I do it:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output encoding="UTF-8" indent="yes" method="xml" /> <xsl:template match="order"> <xsl:copy> <!-- copy all attributes; maybe you don't want this --> <xsl:apply-templates select="@*" /> <!-- copy some elements in a specific order --> <xsl:apply-templates select="customer" /> <xsl:apply-templates select="ship" /> <xsl:apply-templates select="items" /> <xsl:apply-templates select="price" /> <!-- now copy any other children that we haven't explicitly reordered; again, possibly this is not what you want --> <xsl:apply-templates select="*[not(self::customer or self::ship or self::items or self::price)]"/> </xsl:copy> </xsl:template> <xsl:template match="ship"> <xsl:copy> <xsl:apply-templates select="@*" /> <xsl:apply-templates select="street" /> <xsl:apply-templates select="city" /> <xsl:apply-templates select="zipcode" /> <xsl:apply-templates select="country" /> <xsl:apply-templates select="*[not(self::street or self::city or self::zipcode or self::country)]"/> </xsl:copy> </xsl:template> <xsl:template match="items"> <xsl:copy> <xsl:apply-templates select="@*" /> <xsl:apply-templates select="itemno" /> <xsl:apply-templates select="quantity" /> <xsl:apply-templates select="*[not(self::itemno or self::quantity)]"/> </xsl:copy> </xsl:template> <!-- this is the identity transform: it copies everything that isn't matched by a more specific template --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet> 

Notice how many less assumptions this template makes in the structure of your source XML. It is also much easier to change: for example, if you want to disable or rename a specific element that may itself have children, you simply add a new xsl:template that matches this element, do whatever you need and xsl:apply-templates to children.

You should learn more about this XSLT template because it is very versatile and makes creating templates much less tedious and your templates are much less fragile.

+9
source

How can I make the order of all nodes as expected?

The short answer . Using <xsl:apply-templates/> and <xsl:template> instead of <xsl:copy-of>


Here is the complete conversion :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="order"> <xsl:copy> <xsl:apply-templates select="customer"/> <xsl:apply-templates select="*[not(self::customer)]"/> </xsl:copy> </xsl:template> <xsl:template match="ship"> <xsl:copy> <xsl:apply-templates select="street"/> <xsl:apply-templates select="city"/> <xsl:apply-templates select="zipcode"/> <xsl:apply-templates select="country"/> </xsl:copy> </xsl:template> <xsl:template match="items"> <xsl:copy> <xsl:apply-templates select="itemno"/> <xsl:apply-templates select="quantity"/> </xsl:copy> </xsl:template> </xsl:stylesheet> 

when this conversion is applied to the provided XML document :

 <order xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" > <ship> <zipcode>78712</zipcode> <street>1234 Main Street</street> <country>CN</country> <city>Beijing</city> </ship> <items> <quantity>1</quantity> <itemno>1234</itemno> </items> <items> <quantity>3</quantity> <itemno>1235</itemno> </items> <price>456</price> <customer>Tom Hill</customer> </order> 

required, the correct result is obtained :

 <order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <customer>Tom Hill</customer> <ship> <street>1234 Main Street</street> <city>Beijing</city> <zipcode>78712</zipcode> <country>CN</country> </ship> <items> <itemno>1234</itemno> <quantity>1</quantity> </items> <items> <itemno>1235</itemno> <quantity>3</quantity> </items> <price>456</price> </order> 

Explanation

 <xsl:copy-of select="someElement"/> 

copies the entire subtree rooted with someElement in the same way as (and if we had an instruction that rebuilds the descendants, how will this instruction know the order we want?).

To change the order of siblings, we must specify the new desired order.

This can be done by writing a sequence of <xsl:apply-templates> instructions, each of which will select the desired element in the right order. We could write <xsl:copy-of> instructions, but only to copy elements whose descendants we want to leave in their original order.

+1
source

Source: https://habr.com/ru/post/1383550/


All Articles