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'.
Related
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>
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 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.
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>
I want to get the total of a specific student from following 2 XML files:
--- File: mark.xml ----
<marks>
<mark type="HD">10</mark>
<mark type="D">8</mark>
<mark type="C">5</mark>
</marks>
--- File: studentRecord.xml ---
<students>
<student id="1234">
<grade>HD</grade>
</student>
<student id="1234">
<grade>C</grade>
</student>
<student id="1111">
<grade>D</grade>
</student>
</students>
How can I get the total mark of the student having id 1234? it should be 15.
Here is a short and easy XSLT solution (actually it is just XPath):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:MarkValues>
<marks>
<mark type="HD">10</mark>
<mark type="D">8</mark>
<mark type="C">5</mark>
</marks>
</my:MarkValues>
<xsl:template match="/*">
<xsl:value-of select=
"sum(document('')/*
/my:MarkValues/*/*
[#type = current()/*
[#id='1234']/grade
]
)"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied to the provided XML document (named "studentRecord.xml") :
<students>
<student id="1234">
<grade>HD</grade>
</student>
<student id="1234">
<grade>C</grade>
</student>
<student id="1111">
<grade>D</grade>
</student>
</students>
the wanted answer is produced:
15
If you want to keep the marks values in a separate file (not embedded into the XSLT stylesheet as above), the XPath expression should be slightly changed (just the argument to the document() function:
sum(document('mark.xml')/*/*
[#type = current()/*
[#id='1234']/grade
]
)
Explanation:
Using the XSLT document() function.
Using the XSLT current() function.
Using the XPath sum() function.
In the following transform I build first a variable with the gathered values and then XPath sum() function is used to get the result. It's not as elegant and smart as #Dimitre approach but I'd like to post anyway :)
XSLT 2.0 tested under Saxon-HE 9.2.1.1J
<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="lookup" select="document('lookup.xml')/marks"/>
<xsl:template match="students">
<xsl:variable name="values">
<values>
<xsl:for-each select="student[#id='1234']">
<value><xsl:value-of select="$lookup/mark[#type=current()/grade]"/></value>
</xsl:for-each>
</values>
</xsl:variable>
<xsl:value-of select="sum($values//value)"/>
</xsl:template>
</xsl:stylesheet>
The result applied on the input data provided in the question is just the sum:
15
Try this C# code:
var studenst = XElement.Load("studentRecord.xml");
var marks= XElement.Load("marks.xml");
Dictionary<string, int> marksDic = new Dictionary<string, int>();
foreach (XElement m in marks.Descendants())
{
if (m.Attribute("type") != null)
marksDic.Add(m.Attribute("type").Value, int.Parse(m.Value));
}
foreach (XElement s in studenst.Descendants().Where(x=>(x.Attribute("id") !=null ?int.Parse(x.Attribute("id").Value):0)==id))
{
Console.WriteLine(marksDic.Where(x => x.Key == s.Value)
.Select(x => x.Value).Single());
}
}