XSLT: Add few Variables to List - xslt

I am trying to Add a variable to a list from XML using XSTL and create the merged tag but it is not happening.
Source XML.
<Result>
<Data>
<Pass>true</Pass>
<Data>
<account>
<accountNumber>1111</accountNumber>
</account>
<account>
<accountNumber>2222</accountNumber>
</account>
</Result>
Requirement is that the output tag should have the list with Pass tag value like below.
<Student>
<accountNumber>1111</accountNumber>
<Pass>true</Pass>
<Student>
<Student>
<accountNumber>2222</accountNumber>
<Pass>true</Pass>
<Student>
Current XSLT file is giving the list but I am unable to add Pass Tag.
<xsl:template match = "Root/Result">
<xsl:apply-templates select="account"/>
</xsl:template>
<xsl:template match = "account">
<Student>
<accountNumber><xsl:value-of select="accountNumber" /></accountNumber>
</Student>
</xsl:template>

Try this:
<xsl:template match = "account">
<Student>
<accountNumber><xsl:value-of select="accountNumber" /></accountNumber>
<xsl:copy-of select="preceding-sibling::Pass"/>
</Student>
</xsl:template>
Update. Since the source data changed this is what you then need:
<xsl:template match = "account">
<Student>
<accountNumber><xsl:value-of select="accountNumber" /></accountNumber>
<xsl:copy-of select="preceding-sibling::Data/Pass"/>
</Student>
</xsl:template>

Related

Generate XSL (Denormalise format) for Nested XML

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>

How to get a position of specific node

There is a XML structure:
<school>
<class>
<student>
<ID>0023</ID>
<NAME>JOHN</NAME>
</student>
<student>
<ID>0067</ID>
<NAME>STEVE</NAME>
</student>
<student>
<ID>0094</ID>
<NAME>MARY</NAME>
</student>
<student>
<ID>0108</ID>
<NAME>SARA</NAME>
</student>
<student>
<ID>0234</ID>
<NAME>MARTIN</NAME>
</student>
</class>
</school>
I need to get a position of specific node. If I ask for Steve then I receive 2 as a result. Because this is a second student from my list (sort by ID).
<steve_seq> 2 </steve_seq>
Thanks in advance.
I've tried this:
<steve_seq>
<xsl:value-of select="count(school/class/student/name[.='steve']/preceding-sibling::name)+1"/>
</steve_seq>
If you are willing to hard-code the name into your stylesheet, you could do:
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="/">
<steve_seq>
<xsl:value-of select="count(school/class/student[NAME='STEVE']/preceding-sibling::student) + 1"/>
</steve_seq>
</xsl:template>
</xsl:stylesheet>
Note that XSLT is case sensitive: name is not the same thing as NAME and 'steve' is not the same as 'STEVE'.

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