Generate XSL (Denormalise format) for Nested XML - xslt

I need to flatten my XML as the file size very huge and nested level, I am creating the XSL file manually. Below is my sample scenario of content of the XML file-
<StudentDetail>
<SchoolName>SSHPS</SchoolName>
<SchoolEstablishedYear>1990</SchoolEstablishedYear>
<ClassDetails>
<ClassDetail>
<ClassStartedYear>1990</ClassStartedYear>
<Section ID="12345">
<SectioName>Section A</SectioName>
<Students>
<Student ID="1">
<StudentName>John</StudentName>
<Address>
<HomeNumber>10</HomeNumber>
<StreetName>Avenue</StreetName>
</Address>
</Student>
<Student ID="2">
<StudentName>Steve</StudentName>
</Student>
</Students>
</Section>
<Section ID="123456">
<SectioName>Section B</SectioName>
<Students>
<Student ID="100">
<StudentName>Dia</StudentName>
<Age>6</Age>
</Student>
<Student ID="101">
<StudentName>Kevin</StudentName>
</Student>
</Students>
</Section>
</ClassDetail>
<ClassDetail>
<ClassStartedYear>1995</ClassStartedYear>
<Section ID="543466">
<SectioName>Section A</SectioName>
<Students>
<Student ID="200">
<StudentName>Dia</StudentName>
<Muncipality>
<AreaCode>100</AreaCode>
<Areaname>GRAND</Areaname>
</Muncipality>
</Student>
<Student ID="201">
<StudentName>Liva</StudentName>
</Student>
</Students>
</Section>
<Section ID="7543466">
<SectioName>Section A</SectioName>
<Students>
<Student ID="300">
<StudentName>Zane</StudentName>
</Student>
<Student ID="301">
<StudentName>Susan</StudentName>
</Student>
</Students>
</Section>
</ClassDetail>
</ClassDetails>
</StudentDetail>
Below is the required format of the XML-
<StudentDetail>
<Student>
<SchoolName>SSHPS</SchoolName>
<SchoolEstablishedYear>1990</SchoolEstablishedYear>
<ClassStartedYear>1990</ClassStartedYear>
<SectionID>12345</SectionID>
<SectioName>Section A</SectioName>
<StudentID>1</StudentID>
<StudentName>John</StudentName>
<Address_HomeNumber>10</Address_HomeNumber>
<Address_StreetName>Avenue</Address_StreetName>
<Age> </Age>
<Muncipality_AreaCode></Muncipality_AreaCode>
<Muncipality_Areaname></Muncipality_Areaname>
</Student>
.
.
.
<Student>
<SchoolName>SSHPS</SchoolName>
<SchoolEstablishedYear>1990</SchoolEstablishedYear>
<ClassStartedYear>1995</ClassStartedYear>
<SectionID>7543466</SectionID>
<SectioName>Section A</SectioName>
<StudentID>100</StudentID>
<StudentName>Dia</StudentName>
<Address_HomeNumber></Address_HomeNumber>
<Address_StreetName></Address_StreetName>
<Age></Age>
<Muncipality_AreaCode>100</Muncipality_AreaCode>
<Muncipality_Areaname>GRAND</Muncipality_Areaname>
</Student>
</StudentDetail>
I have generated the XSL template, I am not able to load this as there is some error within this-
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<StudentDetail>
<xsl:for-each select="StudentDetail/ClassDetails">
<Student>
<SchoolName><xsl:value-of select="StudentDetail/SchoolName"/></SchoolName>
<SchoolEstablishedYear><xsl:value-of select="StudentDetail/SchoolEstablishedYear"/></SchoolEstablishedYear>
<ClassStartedYear><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/ClassStartedYear"/></ClassStartedYear>
<StudentID><xsl:value-of select="StudentDetail/ClassDetails/Section/#ID"/></StudentID>
<SectioName><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/SectionName"/></SectioName>
<StudentID><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/#ID"/></StudentID>
<StudentName><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student"/></StudentName>
<Address_HomeNumber><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Address/HomeNumber"/></Address_HomeNumber>
<Address_StreetName><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Address/StreetName"/></Address_StreetName>
<Age><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Age"/></Age>
<Muncipality_AreaCode><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Muncipality/AreaCode"/></Muncipality_AreaCode>
<Muncipality_Areaname><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Muncipality/Areaname"/></Muncipality_Areaname>
</Student>
</xsl:for-each>
</StudentDetail>
</xsl:template>
I am new to handling the XML, I am stuck with handling the nested XML

The main problem with your stylesheet is that you are creating a Student for each ClassDetails instead of for each Student.
Instead of:
<xsl:for-each select="StudentDetail/ClassDetails">
<Student>
<!-- data -->
</Student>
</xsl:for-each>
you should be doing:
<xsl:for-each select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student">
<Student>
<!-- data -->
</Student>
</xsl:for-each>
Then, within the Student element, you need to retrieve data from the ancestor School, ClassDetail and Section parts, as well as from the child elements of the current Student node.
In order to minimize the need to repeatedly navigate up and down the tree, I would suggest putting the ancestor parts details in variables and access them from there:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/StudentDetail">
<xsl:copy>
<xsl:variable name="school-details" select="SchoolName | SchoolEstablishedYear"/>
<xsl:for-each select="ClassDetails/ClassDetail">
<xsl:variable name="class-details" select="ClassStartedYear"/>
<xsl:for-each select="Section">
<xsl:variable name="section-details">
<SectionID>
<xsl:value-of select="#ID"/>
</SectionID>
<xsl:copy-of select="SectioName"/>
</xsl:variable>
<xsl:for-each select="Students/Student">
<xsl:copy>
<xsl:copy-of select="$school-details | $class-details"/>
<xsl:copy-of select="$section-details"/>
<StudentID>
<xsl:value-of select="#ID"/>
</StudentID>
<xsl:copy-of select="StudentName"/>
<Address_HomeNumber>
<xsl:value-of select="Address/HomeNumber"/>
</Address_HomeNumber>
<Address_StreetName>
<xsl:value-of select="Address/StreetName"/>
</Address_StreetName>
<Age>
<xsl:value-of select="Age"/>
</Age>
<Muncipality_AreaCode>
<xsl:value-of select="Muncipality/AreaCode"/>
</Muncipality_AreaCode>
<Muncipality_Areaname>
<xsl:value-of select="Muncipality/Areaname"/>
</Muncipality_Areaname>
</xsl:copy>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Firstly, I'm surprised that anyone would want to turn this nicely structured input into this badly structured output. But we're not here to discuss that.
Secondly, I'm confused by the statement that you are "generating the XSL manually". I would have thought you were either creating it manually, or you were generating it programmatically, and it's not clear which of those is the case here.
Third, you've told us there is "some error" in your generated XSL, but you haven't told us what that error is. The only error I can see is a missing close tag for the xsl:stylesheet element, and that's presumably a typo. If you're getting an error, tell us what the error is.
Fourth, there seems to be a much simpler approach. As far as I can see, you can achieve the desired output by applying three rules:
If an element has child elements, just process its children
If an element has an ID attribute, change <X ID="x"/> to <XID>x</XID> and then process its children
If an element has a text node child, copy it unchanged.
The first rule corresponds to the default XSLT processing rule; the other two rules can be simply expessed in XSLT as:
<xsl:template match="*[#ID]">
<xsl:element name="{name()}ID">
<xsl:value-of select="#ID"/>
</xsl:element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[text()]">
<xsl:copy-of select="."/>
</xsl:template>

Related

XSLT Generating multiple output

I have an xml document. from that xml I need to generate multiple xml using xslt.
for example.
<Class>
<Student>
<name>First</name>
<age></age>
</Student>
<Student>
<name>Second</name>
<age></age>
</Student>
</Class>.
From this xml I need to generate one xml with First student and another with Second student.
You can use this spiltter
<xsl:template match="Class">
<xsl:for-each select="Student">
<xsl:result-document href="{position()}.xml" method="xml">
<Class>
<xsl:copy-of select="."></xsl:copy-of>
</Class>
</xsl:result-document>
</xsl:for-each>
</xsl:template>

XSLT To Bring Child Elements into Parent As Attributes And Keep Existing Parent Attribute

I'm stumped and am very new to XSLT. A little direciton would be greatly appreciated. My ultimate objective is to bring the firstName, lastName, and gender elements under the student element up into that element as attributes, but also want to retain the StudentID attribute that already exists there. I'd also like that "year_Collection" to disappear.
I started with this XML document
<?xml version="1.0" encoding="UTF-8"?>
<Report xmlns="Upload" Name="Upload">
<student StudentID="123456">
<firstName firstName="John"/>
<lastName lastName="Johnson"/>
<gender gender="M"/>
<year_Collection>
<year value="2013">
<term hoursEarned="18.00" hoursAttempted="18.00" termCode="S1"/>
</year>
</year_Collection>
</student>
</Report>
My desired output looks like this
<?xml version="1.0" encoding="UTF-8"?>
<Report xmlns="Upload" Name="Upload">
<student gender="M" lastName="Johnson" firstName="John" StudentID="123456">
<year value="2013">
<term hoursEarned="18.00" hoursAttempted="18.00" termCode="S1"/>
</year>
</student>
</Report>
I was able to use this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:r="Upload" exclude-result-prefixes="r" extension-element-prefixes="r">
<xsl:template match="r:student">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
To get here:
<?xml version="1.0" encoding="UTF-8"?>
<Report xmlns="Upload" Name="Upload">
<student year_Collection="" gender="M" lastName="Johnson" firstName="John">
<year value="2013">
<term hoursEarned="18.00" hoursAttempted="18.00" termCode="S1"/>
</year>
</student>
</Report>
But it overwrites the StudentID attribute that already exists. How do I get the elements to come up as attributes, but not overwrite StudentID?
Also, that pesky year_collection element won't disappear with this, but I can split that into a separate question if that's a better way to go about it.
<xsl:template match="r:year_Collection">
<xsl:apply-templates/>
</xsl:template>
But it overwrites the StudentID attribute that already exists.
No, it doesn't. The problem is you're not copying it. You should start your template with:
<xsl:template match="r:student">
<xsl:copy>
<xsl:copy-of select="#StudentID"/>
or, if you prefer:
<xsl:template match="r:student">
<xsl:copy>
<xsl:copy-of select="#*"/>
to copy any and all attributes the Student has.
Regarding the problem with<year_Collection>, you should try to turn only leaf nodes into attributes, for example:
<xsl:template match="r:student">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="*/#*"/>
<xsl:copy-of select="*/*[#*]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Of course, if your structure is known, naming the required nodes explicitly would be much better, e.g;
<xsl:template match="r:student">
<xsl:copy>
<xsl:copy-of select="#StudentID | r:firstName/#firstName | r:lastName/#lastName | r:gender/#gender"/>
<xsl:copy-of select="r:year_Collection/r:year"/>
</xsl:copy>
</xsl:template>

XSLT Group Count

I am facing a problem while creating an XSLT for the below transformation. I am relatively new to XSLT transformations. The problem is that i want to count the number of students from distinct countries in the input. I have tried counting based on conditions but it doesn't work as this counting is grouped. Can someone tell me if this can be done is XSLT 1.0.
My Input is
<ns0:DetailsResponse xmlns:ns0="http://MySchema.XSLTSchema">
<Class>1</Class>
<Students>
<StudentName>John</StudentName>
<StudentSurname>Doe</StudentSurname>
<Country>
<CountryName>UK</CountryName>
</Country>
</Students>
<Students>
<StudentName>Cherry</StudentName>
<StudentSurname>Blossom</StudentSurname>
<Country>
<CountryName>US</CountryName>
</Country>
</Students>
<Students>
<StudentName>Ankit</StudentName>
<StudentSurname>Sood</StudentSurname>
<Country>
<CountryName>INDIA</CountryName>
</Country>
</Students>
<Students>
<StudentName>Peter</StudentName>
<StudentSurname>Scott</StudentSurname>
<Country>
<CountryName>UK</CountryName>
</Country>
</Students>
<Students>
<StudentName>Joe</StudentName>
<StudentSurname>Carter</StudentSurname>
<Country>
<CountryName>UK</CountryName>
</Country>
</Students>
<Students>
<StudentName>Anu</StudentName>
<StudentSurname>Mehta</StudentSurname>
<Country>
<CountryName>INDIA</CountryName>
</Country>
</Students>
</ns0:DetailsResponse>
and I want my Output to be like
Output
<ns0:Root xmlns:ns0="http://MySchema.XSLTSchema_Destination">
<DestinationClass>DestinationClass_0</DestinationClass>
<Countries>
<CountryWiseCount>
<Country>INDIA</Country>
<Count>2</Count>
</CountryWiseCount>
<CountryWiseCount>
<Country>UK</Country>
<Count>3</Count>
</CountryWiseCount>
<CountryWiseCount>
<Country>US</Country>
<Count>1</Count>
</CountryWiseCount>
</Countries>
</ns0:Root>
If you are using XSLT 1.0, then Muenchian Grouping will be your friend here.
You are grouping students by country name, so you define a key to look up students like so
<xsl:key name="students" match="Students" use="Country/CountryName"/>
Then, to get the distinct countries, you would match the Students elements that happen to be the elements that occur first in the key for their given country.
<xsl:template
match="Students[generate-id() = generate-id(key('students', Country/CountryName)[1])]">
Then to get the count of all the students for that country, you can just count the key:
<xsl:value-of select="count(key('students', Country/CountryName))"/>
Here is the full XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="students" match="Students" use="Country/CountryName"/>
<xsl:template match="Students[generate-id() = generate-id(key('students', Country/CountryName)[1])]">
<CountryWiseCount>
<Country>
<xsl:value-of select="Country/CountryName"/>
</Country>
<Count>
<xsl:value-of select="count(key('students', Country/CountryName))"/>
</Count>
</CountryWiseCount>
</xsl:template>
<xsl:template match="Students"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<ns0:DetailsResponse xmlns:ns0="http://MySchema.XSLTSchema">
<Class>1</Class>
<CountryWiseCount>
<Country>UK</Country>
<Count>3</Count>
</CountryWiseCount>
<CountryWiseCount>
<Country>US</Country>
<Count>1</Count>
</CountryWiseCount>
<CountryWiseCount>
<Country>INDIA</Country>
<Count>2</Count>
</CountryWiseCount>
</ns0:DetailsResponse>
Note the use of the template <xsl:template match="Students"/> which matches the Students elements which are not first in the key, to stop them being output. The XSLT will always give priority to the more specific template (with the xpath expression), so this template won't ignore everything.
Obviously you would need to extend the XSLT with a template to match class too, but I am sure you can work that out.

XSLT Convert generic XML

I have an XML file generated using javax.sql.rowset.WebRowSet.writeXml which looks like:
<metadata>
This section has column properties like name / label etc
</metadata>
<data>
<currentRow>
<columnValue>Ken</columnValue>
<columnValue>12</columnValue>
<columnValue>USA</columnValue>
</currentRow>
</data>
I want to convert this to look like:
<Class>
<Student>
<name>Ken</name>
<ID>12</ID>
<location>USA</location>
</Student>
</Class>
How can I do the conversion? I need this to transform the XML to a HTML table.
The following stylesheet produces the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Class>
<xsl:apply-templates select="/*/data/currentRow" />
</Class>
</xsl:template>
<xsl:template match="currentRow">
<Student>
<xsl:apply-templates select="columnValue" />
</Student>
</xsl:template>
<xsl:template match="columnValue[1]">
<name><xsl:apply-templates/></name>
</xsl:template>
<xsl:template match="columnValue[2]">
<ID><xsl:apply-templates/></ID>
</xsl:template>
<xsl:template match="columnValue[3]">
<location><xsl:apply-templates/></location>
</xsl:template>
</xsl:stylesheet>
Note: Added a root node to the given source to make it well-formed.

XSLT filtering nodes on conditional logic

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?
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.
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>