I am trying to transform an input XML where all parent nodes say 'x' need to be extracted, sorted and then reordered in the final output XML based on their subnode say 'y' value. Below is my XML and I am trying to modify it so that the final XML will have the elements in ascending order where the sorting is done based on number values in the xpath notes/notesBODY/group/text
, basically the element <text>1. </text>
present in all notes
elements. But the output XML is not as expected as I can't seem to figure how to use the sort exactly in XSLT. Any help appreciated.
Input XML:
<root>
<group>
<text>group1</text>
</group>
<group>
<text>group2</text>
</group>
<notes>
<notesText>notes1</notesText>
<notesBODY>
<group>
<text>2. </text>
<text>notesbody1</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes2</notesText>
<notesBODY>
<group>
<text>1. </text>
<text>notesbody2</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes3</notesText>
<notesBODY>
<group>
<text>3. </text>
<text>notesbody3</text>
</group>
</notesBODY>
</notes>
<group>
<text>group3</text>
<notes>
<notesText>notes4</notesText>
<notesBODY>
<group>
<text>4. </text>
<text>notesbody4</text>
</group>
</notesBODY>
</notes>
</group>
<group>
<text>group4</text>
</group>
<group>
<text>group5</text>
<notes>
<notesText>notes5</notesText>
<notesBODY>
<group>
<text>6. </text>
<text>notesbody5</text>
</group>
</notesBODY>
</notes>
</group>
<notes>
<notesText>notes6</notesText>
<notesBODY>
<group>
<text>5. </text>
<text>notesbody6</text>
</group>
</notesBODY>
</notes>
<group>
<text>group6</text>
</group>
<group>
<text>group7</text>
</group>
<group>
<text>group8</text>
<notes>
<notesText>notes7</notesText>
<notesBODY>
<group>
<text>8. </text>
<text>notesbody7</text>
</group>
</notesBODY>
</notes>
</group>
<notes>
<notesText>notes8</notesText>
<notesBODY>
<group>
<text>7. </text>
<text>notesbody8</text>
</group>
</notesBODY>
</notes>
<group>
<text>group9</text>
</group>
</root>
Expected Output:
<root>
<group>
<text>group1</text>
</group>
<group>
<text>group2</text>
</group>
<group>
<text>group3</text>
</group>
<group>
<text>group4</text>
</group>
<group>
<text>group5</text>
</group>
<group>
<text>group6</text>
</group>
<group>
<text>group7</text>
</group>
<group>
<text>group8</text>
</group>
<group>
<text>group9</text>
</group>
<notes>
<notesText>notes2</notesText>
<notesBODY>
<group>
<text>1. </text>
<text>notesbody2</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes1</notesText>
<notesBODY>
<group>
<text>2. </text>
<text>notesbody1</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes3</notesText>
<notesBODY>
<group>
<text>3. </text>
<text>notesbody3</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes4</notesText>
<notesBODY>
<group>
<text>4. </text>
<text>notesbody4</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes6</notesText>
<notesBODY>
<group>
<text>5. </text>
<text>notesbody6</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes5</notesText>
<notesBODY>
<group>
<text>6. </text>
<text>notesbody5</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes8</notesText>
<notesBODY>
<group>
<text>7. </text>
<text>notesbody8</text>
</group>
</notesBODY>
</notes>
<notes>
<notesText>notes7</notesText>
<notesBODY>
<group>
<text>8. </text>
<text>notesbody7</text>
</group>
</notesBODY>
</notes>
</root>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="notes">
<xsl:apply-templates select="@*" />
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="notesBODY/group/text[position() = 0]" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I think you basically want to use two templates
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="group"/>
<xsl:apply-templates select=".//notes">
<xsl:sort select="notesBODY/group/text[1]"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="group">
<xsl:copy>
<xsl:apply-templates select="* except notes"/>
</xsl:copy>
</xsl:template>
plus the identity transformation obviously you already have.
@ Martin Honnen, however close I tried, for the mock up input xml that I have provided your solution works perfectly to sort the nodes. For my actual input xml I made a slight change, instead of <xsl:template match="root">, I used <xsl:template match="/"> and the 'notes' are then perfectly sorted, thank you! However all the other input XML tags are getting removed, my actual input xml has many other tags at the root level. It also has attributes at the group and notes levels. I had thought the identity template will handle this case, but looks like it isn't. Can you suggest?
@ Martin Honnen, I also noticed attributes of the 'group' tag as in my actual xml are missing from the final output, how do I also copy those?
If there are attributes one
group
orroot
you need to use<xsl:copy><xsl:apply-templates select="@*"/>...</xsl:copy>
(where the...
is what I have shown in the above code). I can't tell why you think you need to change a template's match but expect the code to do the same job, raise that as a separate question perhaps.@ Martin Honnen, I used
<xsl:copy-of select="@*" />
instead of<xsl:apply-templates select="@*"/>
and it resolved to retain attributes. Is this also ok to use ? For the template match I figured out, it was a mistake to use"\"
, I used the parent of thegroup
node and it works perfect!If you know you only need to copy the attributes then of course
xsl:copy-of
is absolutely fine, theapply-templates
is more versatile in the context of using the identity transformation as the base template as that way, once you need to change an attribute, you just add a template doing that.