Reputation: 323
Here is sample xml
<Data version="2.0">
<Group>
<Item>3</Item>
<Item>1</Item>
<Item>2</Item>
</Group>
<Group>
<Item>7</Item>
<Item>5</Item>
</Group>
</Data>
And for ordering nodes in Group by Item value I tried to use the following xsl:
<xsl:template match="/Data">
<xsl:apply-templates select="Group">
<xsl:sort select="Item" />
</xsl:apply-templates>
</xsl:template>
But get only values, even without sorting:
3
1
2
7
5
So the questions are: 1. why sorting not work 2. How to keep all nodes and keep structure of xml?
Upvotes: 2
Views: 130
Reputation: 243449
@Jim's answer is basically correct.
However, applied to a slightly more realistic XML document, such as this:
<Data version="2.0">
<Group>
<Item>3</Item>
<Item>1</Item>
<Item>10</Item>
<Item>2</Item>
</Group>
<Group>
<Item>7</Item>
<Item>5</Item>
</Group>
</Data>
the result produced is clearly not what you want (10 comes before 2 and 3):
<?xml version="1.0" encoding="utf-8"?>
<Data version="2.0">
<Group>
<Item>1</Item>
<Item>10</Item>
<Item>2</Item>
<Item>3</Item>
</Group>
<Group>
<Item>5</Item>
<Item>7</Item>
</Group>
</Data>
Here is a correct solution (that is also slightly shorter):
<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="Group">
<Group>
<xsl:apply-templates select="*">
<xsl:sort data-type="number"/>
</xsl:apply-templates>
</Group>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (above), the wanted, correct result is produced:
<Data version="2.0">
<Group>
<Item>1</Item>
<Item>2</Item>
<Item>3</Item>
<Item>10</Item>
</Group>
<Group>
<Item>5</Item>
<Item>7</Item>
</Group>
</Data>
Explanation: Use of the data-type
attribute of <xsl:sort>
to specify that the sort keys value should be treated as number, not as (the default) string.
Upvotes: 2
Reputation: 86774
This is what you want:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Group">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="Item">
<xsl:sort select="text()" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The first template is the "Identity Template" (Google it) which copies input to output unchanged. Then for the Group node we copy it to the output (<xsl:copy>
), copy its attributes, then copy the nested Item nodes after sorting them. They get copied because the inner <xsl:apply-templates select="Item">
ends up using the Identity template since there's no more-specific template for Item nodes.
Note that if Group
nodes can contain other stuff besides Item
nodes, you'll have to make sure it gets copied as well. The template above would discard them.
Upvotes: 4