XSLT sum by lookup value from another file - xslt

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());
}
}

Related

Split element within xslt file

I have the following input
UK/006/10
US/004/12
And wanted to get the following output.
Country: UK
Code:006
Line: 10
Country: US
Code:004
Line:12
I tried to use following, but I need something simple, like split function. Can someone help on this?
<xsl:value-of select="substring-before(substring-after($User_def_type_4, '/'), '/')" />
Using Invisible XML, you could define a grammar for your text data to map it to XML, then an extension function library like the CoffeeSacks library to Saxon Java can be used in XSLT to parse and post-process the text so that with e.g. the XML input being
<data>UK/006/10
US/004/12</data>
and the XSLT being
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:cs="http://nineml.com/ns/coffeesacks"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:template match="data">
<xsl:apply-templates select="cs:parse-string(cs:grammar-string($grammar), .)/node()"/>
</xsl:template>
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:next-match/>
<xsl:comment xmlns:saxon="http://saxon.sf.net/">Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
<xsl:param name="grammar" as="xs:string" expand-text="no">Countries = Country*.
Country = Name, -'/', Code, -'/', Line, #A?.
Name = ['A'-'Z'],['A'-'Z'].
Code = ['0'-'9'],['0'-'9'],['0'-'9'].
Line = ['0'-'9'],['0'-'9'].</xsl:param>
</xsl:stylesheet>
you get e.g.
<?xml version="1.0" encoding="UTF-8"?>
<Countries>
<Country>
<Name>UK</Name>
<Code>006</Code>
<Line>10</Line>
</Country>
<Country>
<Name>US</Name>
<Code>004</Code>
<Line>12</Line>
</Country>
</Countries>
<!--Run with SAXON HE 11.3 -->
Online sample using Saxon HE 11 Java and the named CoffeeSacks library.

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

Camel with XSLT 2.0 and xsl:result-document keeping files in route destination

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

how to check duplicate element values using xslt

<BookList>
<Book>
<History>
<Type>history</Type>
<Prize>123</Prize>
<Publication>``
<Name>YEAP1</Name>
</Publication>
<RNumber Type="VolumeNumber">11111</RNumber>
<RNumber Type="SupplementNumber">123456</RNumber>
</History>
<chemistry>
<Type>chemistry</Type>
<Prize>333</Prize>
<Publication>
<Name>YEAP</Name>
</Publication>
<RNumber Type="VolumeNumber">11111</RNumber>
<RNumber Type="SupplementNumber">45454</RNumber>
</chemistry>
......
</Book>
</BookList>
There are duplicate VolumnNumber 11111. How to check duplicate VolumnNumber in BoolList xml using xslt . please help on this
I. This can be found using a single XPath expression:
false()
or
/*/*/*/RNumber
[#Type='VolumeNumber'
and
. = ../preceding-sibling::*
/RNumber[#Type='VolumeNumber']
]
Here is a complete XSLT transformation that evaluates this XPath expression and outputs the result of this evaluation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
Duplicate volume numbers exist: <xsl:text/>
<xsl:value-of select=
"false()
or
/*/*/*/RNumber
[#Type='VolumeNumber'
and
. = ../preceding-sibling::*
/RNumber[#Type='VolumeNumber']
]
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<BookList>
<Book>
<History>
<Type>history</Type>
<Prize>123</Prize>
<Publication>``
<Name>YEAP1</Name>
</Publication>
<RNumber Type="VolumeNumber">11111</RNumber>
<RNumber Type="SupplementNumber">123456</RNumber>
</History>
<chemistry>
<Type>chemistry</Type>
<Prize>333</Prize>
<Publication>
<Name>YEAP</Name>
</Publication>
<RNumber Type="VolumeNumber">11111</RNumber>
<RNumber Type="SupplementNumber">45454</RNumber>
</chemistry>
......
</Book>
</BookList>
the wanted, correct result is produced:
Duplicate volume numbers exist: true
II. Solution using keys (generally more efficient):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kVolNum" match="RNumber[#Type='VolumeNumber']"
use="."/>
<xsl:template match="/">
Duplicate volume numbers exist: <xsl:text/>
<xsl:value-of select=
"false()
or
/*/*/*/RNumber
[#Type='VolumeNumber'
and
key('kVolNum',.)[2]
]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct result is produced:
Duplicate volume numbers exist: true