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>
Related
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>
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>
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'.
I am trying to use a XSLT 2.0 transformation to split a XML file into smaller files based on groups of items. To execute the transformation I am using a camel route. My problem is that when the xslt transformation is run the resulting files are saved "outside" the route.
So, for example, given the following camel route:
from("{{input.endpoint}}")
.to("xslt:xslts/ics/MappingMapTostudents.xslt?saxon=true")
.to("{{output.endpoint}}");
I would be expecting the resulting xml files to be created in the {output.endpoint} folder. Unfortunately saxon is saving the files in the root folder where the executable (camel app) is. How can I have the files saved to {{output.endpoint}} or better, passed on to the following endpoint?
Following an XML example:
<?xml version="1.0" encoding="UTF-8"?>
<class>
<students>
<student>
<firstname>Albert</firstname>
<group>A</group>
</student>
<student>
<firstname>Isaac</firstname>
<group>A</group>
</student>
<student>
<firstname>Leonardo</firstname>
<group>B</group>
</student>
<student>
<firstname>Enrico</firstname>
<group>B</group>
</student>
<student>
<firstname>Marie</firstname>
<group>C</group>
</student>
<student>
<firstname>Rosalind</firstname>
<group>C</group>
</student>
<student>
<firstname>Ada</firstname>
<group>D</group>
</student>
</students>
</class>
The XSLT transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:grp="http://www.altova.com/Mapforce/grouping" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="grp xs fn">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:function name="grp:var2_function">
<xsl:param name="var1_param" as="node()"/>
<xsl:for-each select="$var1_param/student">
<xsl:sequence select="fn:string(group)"/>
</xsl:for-each>
</xsl:function>
<xsl:template match="/">
<xsl:for-each-group select="class/students" group-by="grp:var2_function(.)">
<xsl:variable name="var3_resultof_grouping_key" as="xs:string" select="current-grouping-key()"/>
<xsl:result-document href="{fn:concat($var3_resultof_grouping_key, '.xml ')}" encoding="UTF-8">
<class>
<xsl:for-each select="(current-group()/student)[(fn:string(group) = $var3_resultof_grouping_key)]">
<students>
<student>
<firstname>
<xsl:sequence select="fn:string(firstname)"/>
</firstname>
<group>
<xsl:sequence select="$var3_resultof_grouping_key"/>
</group>
</student>
</students>
</xsl:for-each>
</class>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I know very little about Apache-Camel, and unfortunately questions about the technology on StackOverflow don't seem to have a very high success rate.
The documentation at http://camel.apache.org/xslt.html gives no clue about how to set the base output URI, which is used by Saxon for resolving any relative URI appearing in xsl:result-document/#href.
The simplest approach that comes to mind is to pass a parameter to the transformation, whose value is the absolute path to the output directory:
<xsl:param name="outDir" as="xs:string"/>
...
<xsl:result-document
href="file:///{$outDir}{$var3_resultof_grouping_key}.xml"/>
But someone who knows Apache Camel might be able to suggest a more elegant solution.
You may try to force an output of type "file".
to("xslt:xslts/ics/MappingMapTostudents.xslt?saxon=true&output=file")
Then, as the doc explains it, you have to fill a Camel header with the wanted target file path:
For file you must specify the filename in the IN header with the key
Exchange.XSLT_FILE_NAME which is also CamelXsltFileName. Also any paths leading to the filename must be created beforehand, otherwise an exception is thrown at runtime.
Good luck
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>