Reputation: 127
I want to rearrange an xml with xslt.
The source xml is as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<studentDetails>
<departments>
<name>IT</name>
<name>CSE</name>
<name>EEE</name>
</departments>
<students> // Want to arrange students and marks alternatively
<student>
<name>aaa</name>
<age>20</age>
</student>
<student>
<name>bbb</name>
<age>25</age>
</student>
<student>
<name>ccc</name>
<age>27</age>
</student>
<student>
<name>ddd</name>
<age>23</age>
</student>
<marks>
<maths>60</maths>
<english>65</english>
</marks>
<marks>
<maths>70</maths>
<english>75</english>
</marks>
<marks>
<maths>80</maths>
<english>85</english>
</marks>
<marks>
<maths>90</maths>
<english>95</english>
</marks>
</students>
</studentDetails>
I want the output in this way:
<?xml version="1.0" encoding="UTF-8" ?>
<studentDetails>
<departments>
<name>IT</name>
<name>CSE</name>
<name>EEE</name>
</departments>
<students>
<student>
<name>aaa</name>
<age>20</age>
</student>
<marks>
<maths>60</maths>
<english>65</english>
</marks>
<student>
<name>bbb</name>
<age>25</age>
</student>
<marks>
<maths>70</maths>
<english>75</english>
</marks>
<student>
<name>ccc</name>
<age>27</age>
</student>
<marks>
<maths>80</maths>
<english>85</english>
</marks>
<student>
<name>ddd</name>
<age>23</age>
</student>
<marks>
<maths>90</maths>
<english>95</english>
</marks>
</students>
</studentDetails>
1). The number of <student>
tags are equal to the no of <marks>
2). The sequence of <student>
and <marks>
are linear i.e, the first occurance of <student>
should be followed by first occurance of <marks>
I tried the following xslt:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="countVar" select="count(studentDetails/students/student)"></xsl:variable>
<xsl:variable name="i" select="0"></xsl:variable>
<xsl:template match="/">
<xsl:for-each select="1 to $countVar">
<xsl:variable name="counter" select="$i + 1"></xsl:variable>
<xsl:choose>
<xsl:when test="(position() mod 2) = 0">
<xsl:copy-of select="studentDetails/students/marks[$counter]"></xsl:copy-of>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="studentDetails/students/student[$counter]"></xsl:copy-of>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I got the following error:
Error on line 11 XPTY0020: Required item type of the context item for the child axis is node(); supplied value has item type xs:integer
Online version here: http://xsltransform.net/gWmuiJw
Please tell me where I went wrong. Thanks in advance.! :)
Upvotes: 0
Views: 373
Reputation: 116992
When you do:
<xsl:for-each select="1 to $countVar">
you are switching the context from the XML input to the sequence of generated integers. In this context, the XPath you try to use, "studentDetails/students/marks"
, is meaningless.
Why don't you try something much simpler:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="student">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
<xsl:variable name="i" select="position()" />
<xsl:copy-of select="../marks[$i]"/>
</xsl:template>
<xsl:template match="marks"/>
</xsl:stylesheet>
Note:
IMHO, a format that wraps all details of a student under a single element, for example:
<students>
<student>
<name>aaa</name>
<age>20</age>
<marks>
<maths>60</maths>
<english>65</english>
</marks>
</student>
...
<students>
would be much more useful.
The identity transform template, when applied to the <students>
element, applies templates to all child nodes of <students>
- not only the <student>
nodes.
In your example, the <students>
element also has a comment node among its children (actually, it's a text node, but it makes no difference for the purposes of this explanation). The position of each child node is calculated in the context of the node-set selected by the xsl:apply templates
instruction (this is known as the current node list). As a result, the position of the first <student>
node in your example is actually #2 - since position #1 is taken by the comment node.
This shouldn't be a problem in actual production, if in your real XML input there won't be any child nodes of <students>
other than <student>
. If you cannot be sure of that, add one more template to the stylesheet:
<xsl:template match="students">
<xsl:copy>
<xsl:apply-templates select="student"/>
</xsl:copy>
</xsl:template>
to exclude any nodes other than <student>
from the current node list.
Upvotes: 2