2

I have an Source XML as follows:-

<StudentSet>
 <Student>
  <StudentName>Kapil</StudentName>
  <Subject>English</Subject>
  <Subject>History</Subject>
  <Subject>Mathematics</Subject>
  <Subject>Economics</Subject>
 </Student>
 <Student>
  <StudentName>Atul</StudentName>
  <Subject>English</Subject>
  <Subject>History</Subject>
  <Subject>Economics</Subject>
 </Student>
 <Student>
  <StudentName>Nisha</StudentName>
  <Subject>English</Subject>
  <Subject>History</Subject>
 </Student>
 <Student>
  <StudentName>Manish</StudentName>
 </Student>
</StudentSet>

The rules to be applied are as follows.

1) If a student has enrolled in mathematics, show student name and mathematics

2) If a student has enrolled in economics but not in mathematics, show student name and economics

3) If a student has neither enrolled in mathematics and economics, pick student name and any one subject.

4) If a student has not enrolled for any subject, don't pick the student.

I want to get following output:-

<StudentSet>
 <Student>
  <StudentName>Kapil</StudentName>
  <Subject>Mathematics</Subject>
 </Student>
 <Student>
  <StudentName>Atul</StudentName>
  <Subject>Economics</Subject>
 </Student>
 <Student>
  <StudentName>Nisha</StudentName>
  <Subject>English</Subject>
 </Student>
</StudentSet>

Can anybody please help what XSLT can be used for this?

Kapil
  • 23
  • 1
  • 3
  • 2
    Is this homework? This seems like a homework question to me. – Aaron Klotz Aug 19 '11 at 05:36
  • You should post the XSLT code you already have... – Antonio Pérez Aug 19 '11 at 08:13
  • I am newbie to XSLT. The one i was using was completely wrong. I was using variable and trying to change the value of variable in a for-each loop. After googling I found out that it is not possible to change values of variable in xslt. So that one isn't any good. – Kapil Aug 19 '11 at 08:56
  • Good question, +1. See my answer for a better solution that allows to have the prioritized subjects passed as a parameter. Also, the transformation doesn't grow in complexity and number of templates as the list of prioritized subjects grows. :) You could also easily solve similar problems as: "Show the first two subjects from the prioritized list". – Dimitre Novatchev Aug 19 '11 at 13:12

2 Answers2

4

The easy way is just use templates with predicates, like this:

<xsl:template match="Student[Subject='Economics']">
  <!-- Handle economics students -->
</xsl:template>

<xsl:template match="Student[Subject='Mathematics']">
  <!-- Handle maths students -->
  <!-- overrides maths+econ students, as it comes later -->
</xsl:template>

<xsl:template match="Student[not(Subject='Economics') and not(Subject='Mathematics')]">
  <!-- Handle other students. Use 'Subject[1]' to refer to first subject -->
</xsl:template>

<!-- Handle students with no subject, outputting nothing. -->
<xsl:template match="Student[not(Subject)]" />

In each case, you probably want to do

<xsl:copy> <!-- copies the 'Student' element -->
  <xsl:copy-of select="StudentName" />
  <xsl:copy-of select="Subject[text()='subjectname']" />
  <!-- or -->
  <xsl:copy-of select="Subject[1]" /> <!-- first subject in list -->
</xsl:copy>

There's a shorter and more efficient way, but it's a bit harder to understand for a beginner:

<xsl:template match="Student[Subject='Mathematics']/Subject[not(text()='Mathematics')]" />
<xsl:template match="Student[Subject='Economics' and not(Subject='Mathematics')]/Subject[not(text()='Economics')]" />
<xsl:template match="Student[not(Subject='Economics') and not(Subject='Mathematics')]/Subject[position() != 1]" />
<xsl:template match="Student[not(Subject)]" />

What these do is describe elements that should not be output. For example, the first one finds any Student elements that have a subject of mathematics, and within that finds any Subject elements that do not have the text 'Mathematics'. The template matches these nodes, and outputs nothing. You could actually do this with one large template rule like this:

<xsl:template match="
  Student[Subject='Mathematics']/Subject[not(text()='Mathematics')]
| Student[Subject='Economics' and not(Subject='Mathematics')]/Subject[not(text()='Economics')]
| Student[not(Subject='Economics') and not(Subject='Mathematics')]/Subject[position() != 1]
| Student[not(Subject)]
  " />

Although it starts to get less readable doing it that way.

Flynn1179
  • 11,925
  • 6
  • 38
  • 74
0

This is a simpler solution that works with any number of prioritized subjects and uses only a single template of fixed size It can easily be used for much more difficult problems as: "Show only the first k subjects from the prioritized list of N subjects":

<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:param name="vSubs" select="'x|Economics|Mathematics|'"/>

 <xsl:template match="Student[Subject]">
     <Student>
      <xsl:copy-of select="StudentName"/>

      <xsl:for-each select="Subject">
       <xsl:sort
         select="string-length(substring-before($vSubs,
                                                concat('|',.,'|')
                                                )
                             )"
       data-type="number" order="descending"/>
       <xsl:if test="position()=1">
        <xsl:copy-of select="."/>
       </xsl:if>
      </xsl:for-each>
     </Student>
 </xsl:template>

 <xsl:template match="text()"/>
</xsl:stylesheet>

When applied on the provided XML document:

<StudentSet>
    <Student>
        <StudentName>Kapil</StudentName>
        <Subject>English</Subject>
        <Subject>History</Subject>
        <Subject>Mathematics</Subject>
        <Subject>Economics</Subject>
    </Student>
    <Student>
        <StudentName>Atul</StudentName>
        <Subject>English</Subject>
        <Subject>History</Subject>
        <Subject>Economics</Subject>
    </Student>
    <Student>
        <StudentName>Nisha</StudentName>
        <Subject>English</Subject>
        <Subject>History</Subject>
    </Student>
    <Student>
        <StudentName>Manish</StudentName>
    </Student>
</StudentSet>

the wanted, correct result is produced:

<Student>
   <StudentName>Kapil</StudentName>
   <Subject>Mathematics</Subject>
</Student>
<Student>
   <StudentName>Atul</StudentName>
   <Subject>Economics</Subject>
</Student>
<Student>
   <StudentName>Nisha</StudentName>
   <Subject>English</Subject>
</Student>
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431