How to find specific string within a group of like-named elements - xslt

For the XML below, how would I find which Election, with the latest date if there are multiple elections elected, each employee has chosen. A default value of 'Bronze' would be used if all elections say 'Waive' or when there are no elections for that employee.
Example XML:
<Data>
<Employee>
<First_Name>Homer</First_Name>
<Last_Name>Simpson</<Last_Name>
<Elections>
<Election type="Homer Simpson on 01/01/2020 (Gold Membership) (Waive)"/>
</Elections>
<Elections>
<Election type="Homer Simpson on 01/01/2020 (Silver Membership) (Waive)"/>
</Elections>
</Employee>
<Employee>
<First_Name>Marge</First_Name>
<Last_Name>Simpson</<Last_Name>
<Elections>
<Election type="Marge Simpson on 01/01/2020 (Gold Membership) (Waive)"/>
</Elections>
<Elections>
<Election type="Marge Simpson on 01/03/2020 (Gold Membership) (Elect)"/>
</Elections>
<Elections>
<Election type="Marge Simpson on 01/03/2020 (Silver Membership) (Waive)"/>
</Elections>
</Employee>
<Employee>
<First_Name>Lisa</First_Name>
<Last_Name>Simpson</<Last_Name>
<Elections>
<Election type="Lisa Simpson on 01/01/2020 (Gold Membership) (Elect)"/>
</Elections>
<Elections>
<Election type="Lisa Simpson on 01/01/2020 (Silver Membership) (Waive)"/>
</Elections>
</Employee>
<Employee>
<First_Name>Bart</First_Name>
<Last_Name>Simpson</<Last_Name>
<Elections>
<Election type="Bart Simpson on 01/01/2020 (Gold Membership) (Waive)"/>
</Elections>
<Elections>
<Election type="Bart Simpson on 01/01/2020 (Silver Membership) (Waive)"/>
</Elections>
<Elections>
<Election type="Bart Simpson on 01/01/2020 (Bronze Membership) (Elect)"/>
</Elections>
</Employee>
<Employee>
<First_Name>Maggie</First_Name>
<Last_Name>Simpson</<Last_Name>
<Elections>
<Election type="Lisa Simpson on 01/01/2020 (Silver Membership) (Elect)"/>
</Elections>
<Elections>
<Election type="Lisa Simpson on 01/01/2020 (Gold Membership) (Waive)"/>
</Elections>
</Employee>
<Employee>
<First_Name>Grandpa</First_Name>
<Last_Name>Simpson</<Last_Name>
</Employee>
</Data>
I tried a few things such as a for-each over the Elections/Election tag looking for the string "(Gold Membership) (Elect)" and "(Silver Membership) (Elect)" and setting a variable if it was found but that didn't work.
The desired output would be:
Homer, Simpson, Bronze
Marge, Simpson, Gold
Lisa, Simpson, Gold
Bart, Simpson, Bronze
Grandpa, Simpson, Silver

This is a lot of work.
It would have been considerably simpler if each employee had only one election marked as Elect. But you say there can be more than one, and that only the latest one needs to be considered. And to make it even worse, you're using a date format that cannot be used for sorting as is.
Try something like the following:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/Data">
<xsl:for-each select="Employee">
<!-- names -->
<xsl:value-of select="First_Name" />
<xsl:text>,</xsl:text>
<xsl:value-of select="Last_Name" />
<xsl:text>,</xsl:text>
<!-- membership -->
<xsl:variable name="valid-elections" select="Elections/Election[contains(#type, '(Elect)')]"/>
<xsl:choose>
<xsl:when test="$valid-elections">
<xsl:variable name="memberships" as="element()*">
<xsl:perform-sort>
<!-- sort by reformatted date -->
<xsl:sort select="replace(#type, '.*(\d{2})/(\d{2})/(\d{4}).*', '$3$1$2')" order="descending"/>
<xsl:sequence select="$valid-elections"/>
</xsl:perform-sort>
</xsl:variable>
<!-- extract type from latest membership -->
<xsl:value-of select="replace($memberships[1]/#type, '.*\((Gold|Silver|Bronze) Membership\).*', '$1')" />
</xsl:when>
<xsl:otherwise>Bronze</xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/gWEaSvj

Related

XSLT3 Streaming for appending integer position of node

I have a large XML file to transform using XSLT to append the integer position of sibling node . I’m using XSLT3 streaming and accumulators. I did get desired output. However, my code looks so lengthy that I’m unable to simplify my code. I also need to group same sibling nodes as sibling nodes in the source xml is not grouped always. Could someone help me here please?
Requirement: Sibling nodes such as Positions, Payments etc.. need to be appended with their corresponding integer position such as <Locations1>, <Locations2>etc.<Payments1>,< Payments2> etc..
Now that I have declared two accumulators, each for each sibling nodes. However, my source XML has many sibling nodes.. I’m not sure if I need to use as many accumulators and template match as my sibling nodes.
Input XML
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Locations>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations>
<Locations>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1500</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1800</amount>
<currency>USD</currency>
</Payments>
</Member>
</Members>
``
Expected Output
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
``
Current code
``
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
<xsl:accumulator name="loc-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Locations" select="$value + 1"/>
</xsl:accumulator>
<xsl:accumulator name="pay-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Payments" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="Locations">
<xsl:element name="Locations_{accumulator-before('loc-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="Payments">
<xsl:element name="Payments_{accumulator-before('pay-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
``
Current Output
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
If you want to group the Member child elements by node-name() then I think you need to wrap the xsl:for-each-group into xsl:fork:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="counters"/>
<xsl:accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*"
select="map:put($value, node-name(), if (map:contains($value, node-name())) then map:get($value, node-name()) + 1 else 1)"/>
</xsl:accumulator>
<xsl:template match="Member">
<xsl:copy>
<xsl:fork>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</xsl:fork>
</xsl:copy>
</xsl:template>
<xsl:template match="Member/*">
<xsl:element name="{node-name()}_{accumulator-before('counters')(node-name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This approach only shows the grouping, it doesn't try to special case Name elements or some other way to not output an index if there is only one such element.
Firstly, my sympathy. XML that uses names like Payments_1 and Payments_2 is really bad news, someone is going to hate you for generating it like this. But if that's the kind of XML you've been told to produce, I guess it's not your job to question it.
As far as the requirements are concerned, you haven't made it clear whether the various kinds of sibling nodes are always grouped as in your example (all Locations, then all Payments, etc), or whether they can be interleaved.
One way you might be able to reduce the volume of code is by having a single accumulator holding a map. The map would use element names as the key and the current sibling count for that element as the value.
<accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*" select="map:put($value, node-name(.), if (map:contains($value, node-name(.)) then map:get($value, node-name(.))+1 else 1"/>
</accumulator>
<xsl:template match="Members/*">
<xsl:element name="{name()}_{accumulator-before('counters')(node-name(.))}">
....
Another way to do the conditional map:put is
map:put($value, node-name(.), ($value(node-name(.)), 0)[1] + 1)

Handling Multiple For Loop in XSLT

Dear Experts,
I have to use multiple for-loops in XSLT.
Presently with my XSLT I generate output with extra nodes at 'GroupDetail'.
Input Request
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:v1="http://xmldefs. ag.com/Applications/eer/V1" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<SOAP-ENV:Header>
<wsa:messageId>04383-34380-3439939</wsa:messageId>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<v1:ProcessDistr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<v1:Group xmlns:ns2="http://xmldefs.ag.com/DD/Commons">
<v1:GroupID>437848</v1:GroupID>
<v1:GroupDetails>
<v1:GroupDetail>
<v1:language>De</v1:language>
<v1:description>Deutsch</v1:description>
</v1:GroupDetail>
<v1:GroupDetail>
<v1:language>En</v1:language>
<v1:description>English</v1:description>
</v1:GroupDetail>
</v1:GroupDetails>
<v1:Status>true</v1:Status>
<v1:Parent>45434554</v1:Parent>
</v1:Group>
<v1:Group xmlns:ns2="http://xmldefs.ag.com/DD/Commons">
<v1:GroupID>437849</v1:GroupID>
<v1:GroupDetails>
<v1:GroupDetail>
<v1:language>Tu</v1:language>
<v1:description>Turkish</v1:description>
</v1:GroupDetail>
<v1:GroupDetail>
<v1:language>Fr</v1:language>
<v1:description>French</v1:description>
</v1:GroupDetail>
</v1:GroupDetails>
<v1:Status>true</v1:Status>
<v1:Parent>45434555</v1:Parent>
</v1:Group>
</v1:ProcessDistr>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Output received with another set of Group details and also 'messageId' is missing.
Output received:
<?xml version="1.0" encoding="UTF-8"?>
<ProcessDistr >
<Group >
<GroupID>437848</GroupID>
<GroupDetails>
<GroupDetail>
<language>De</language>
<description>Deutsch</description>
</GroupDetail>
<GroupDetail>
<language>En</language>
<description>English</description>
</GroupDetail>
<GroupDetail>
<language>Tu</language>
<description>Turkish</description>
</GroupDetail>
<GroupDetail>
<language>Fr</language>
<description>French</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434554</Parent>
</Group>
<Group >
<GroupID>437849</GroupID>
<GroupDetails>
<GroupDetail>
<language>De</language>
<description>Deutsch</description>
</GroupDetail>
<GroupDetail>
<language>En</language>
<description>English</description>
</GroupDetail>
<GroupDetail>
<language>Tu</language>
<description>Turkish</description>
</GroupDetail>
<GroupDetail>
<language>Fr</language>
<description>French</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434555</Parent>
</Group>
<messageId/>
</ProcessDistr>
This is XSLT code which I developed
Used XSLT code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:prof="http://ixult.net/ProfileExchange"
xmlns:sap="http://www.sap.com/sapxsl" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsltc="http://xml.apache.org/xalan/xsltc"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:v1="http://xmldefs. ag.com/Applications/eer/V1"
xmlns:vwsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:ns2="http://xmldefs.vag.com/DD/Commons"
exclude-result-prefixes="vwsu v1 ns2 xsi wsa" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<!-- Output -->
<xsl:output encoding="UTF-8" indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:element name="ProcessDistr">
<xsl:for-each select="//soap:Body/v1:ProcessDistr/v1:Group">
<xsl:element name="Group">
<xsl:element name="GroupID"><xsl:value-of select="v1:GroupID"/></xsl:element>
<xsl:element name="GroupDetails">
<xsl:for-each select="//v1:GroupDetails/v1:GroupDetail">
<xsl:element name="GroupDetail">
<xsl:element name="language"><xsl:value-of select="v1:language"/></xsl:element>
<xsl:element name="Description">
<xsl:value-of select="v1:Description"/></xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
<xsl:element name="Status"><xsl:value-of select="v1:Status"/></xsl:element>
<xsl:element name="Parent"><xsl:value-of select="v1:Parent"/></xsl:element>
</xsl:element>
</xsl:for-each>
<xsl:element name="messageId"><xsl:value-of select="wsa:messageID"/>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output expected:
<?xml version="1.0" encoding="UTF-8"?>
<ProcessDistr >
<Group >
<GroupID>437848</GroupID>
<GroupDetails>
<GroupDetail>
<language>De</language>
<description>Deutsch</description>
</GroupDetail>
<GroupDetail>
<language>En</language>
<description>English</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434554</Parent>
</Group>
<Group >
<GroupID>437849</GroupID>
<GroupDetails>
<GroupDetail>
<language>Tu</language>
<description>Turkish</description>
</GroupDetail>
<GroupDetail>
<language>Fr</language>
<description>French</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434555</Parent>
</Group>
<messageId>04383-34380-3439939</messageId>
</ProcessDistr>
Please help me on this code
Thank you very much.
With Best Regards,
Sateesh N
The main problem of the provided code is not on line 20, although fixing this helps get the wanted output.
The main problem is that the code doesn't make use of the powerful XSLT processing model, using which the solution can be expressed without any <xsl:for-each> instructions. Also, the code can be shrunk and made more compact, understandable and maintainable.
Here is a starter solution (30 lines) that shows how this can be done:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v1="http://xmldefs. ag.com/Applications/eer/V1" xmlns:wsa="http://www.w3.org/2005/08/addressing"
exclude-result-prefixes="soap-env wsa v1">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:apply-templates select="soap-env:Body"/>
</xsl:template>
<xsl:template match="v1:ProcessDistr">
<ProcessDistr>
<xsl:apply-templates/>
<xsl:apply-templates select="/*/soap-env:Header/wsa:messageId"/>
</ProcessDistr>
</xsl:template>
<xsl:template match="v1:* | wsa:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="soap-env:*"><xsl:apply-templates/></xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:v1="http://xmldefs. ag.com/Applications/eer/V1" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<SOAP-ENV:Header>
<wsa:messageId>04383-34380-3439939</wsa:messageId>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<v1:ProcessDistr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<v1:Group xmlns:ns2="http://xmldefs.ag.com/DD/Commons">
<v1:GroupID>437848</v1:GroupID>
<v1:GroupDetails>
<v1:GroupDetail>
<v1:language>De</v1:language>
<v1:description>Deutsch</v1:description>
</v1:GroupDetail>
<v1:GroupDetail>
<v1:language>En</v1:language>
<v1:description>English</v1:description>
</v1:GroupDetail>
</v1:GroupDetails>
<v1:Status>true</v1:Status>
<v1:Parent>45434554</v1:Parent>
</v1:Group>
<v1:Group xmlns:ns2="http://xmldefs.ag.com/DD/Commons">
<v1:GroupID>437849</v1:GroupID>
<v1:GroupDetails>
<v1:GroupDetail>
<v1:language>Tu</v1:language>
<v1:description>Turkish</v1:description>
</v1:GroupDetail>
<v1:GroupDetail>
<v1:language>Fr</v1:language>
<v1:description>French</v1:description>
</v1:GroupDetail>
</v1:GroupDetails>
<v1:Status>true</v1:Status>
<v1:Parent>45434555</v1:Parent>
</v1:Group>
</v1:ProcessDistr>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
the wanted, correct result is produced:
<ProcessDistr>
<Group>
<GroupID>437848</GroupID>
<GroupDetails>
<GroupDetail>
<language>De</language>
<description>Deutsch</description>
</GroupDetail>
<GroupDetail>
<language>En</language>
<description>English</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434554</Parent>
</Group>
<Group>
<GroupID>437849</GroupID>
<GroupDetails>
<GroupDetail>
<language>Tu</language>
<description>Turkish</description>
</GroupDetail>
<GroupDetail>
<language>Fr</language>
<description>French</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434555</Parent>
</Group>
<messageId>04383-34380-3439939</messageId>
</ProcessDistr>
XSLT is a wonderful language and when its full power is used it gives us short and elegant solutions. There are good learning resources waiting to be uncovered.
The main problem with your XSLT is this (line 20):
<xsl:for-each select="//v1:GroupDetails/v1:GroupDetail">
A path that starts with // selects all descendants of the root node, regardless of the current context. You only want to process the descendants of the current v1:Group, so you need to change it to:
<xsl:for-each select="v1:GroupDetails/v1:GroupDetail">
Note also that XML is case-sensitive:
<xsl:value-of select="v1:Description"/>
will not return the value of an element named v1:description.
I would also recommend using literal result elements instead of the xsl:element instruction. Use xsl:element when the name of the element needs to be determined at runtime.

Finding Annual Salary of Employees in XSLT

Hi I am relatively new to XSLT (used to procedural programming languages) but finding it difficult to understand how I can accomplish that in XSLT and will appreciate any help:
The xml, I want to transform follows - Is nothing more than a listing of employees with their monthly salary changes. The objective is to determine the annual salary for 2015.
<employees>
<employee>
<id>E1</id>
<hiredt>2000-01-01</hiredt>
<salaryhistory>
<change>
<efffrom>2000-01-01</efffrom>
<monthlypay>4000</monthlypay>
</change>
<change>
<efffrom>2014-01-01</efffrom>
<monthlypay>5000</monthlypay>
</change>
<change>
<efffrom>2015-02-01</efffrom>
<monthlypay>6000</monthlypay>
</change>
<change>
<efffrom>2015-07-01</efffrom>
<monthlypay>7000</monthlypay>
</change>
</salaryhistory>
</employee>
<employee>
<id>E2</id>
<hiredt>2015-03-01</hiredt>
<salaryhistory>
<change>
<efffrom>2015-03-01</efffrom>
<monthlypay>5000</monthlypay>
</change>
</salaryhistory>
</employee>
</employees>
The objective is to compute the annual salary for all employees for 2015 and transform into the following XML document
<employees>
<employee>
<id>E1</id>
<annualsal>77000</annualsal>
</employee>
<employee>
<id>E2</id>
<annualsal>50000</annualsal>
</employee>
</employees>
Explanation on computation.
Computation for E1
5000 * 1 month = 5000
6000 * 5 months = 30000
7000 * 6 months = 42000
Total 77000
Computation for E2
5000 * 10 months = 50,000 employee started on March 1,2015.
Any guidance will be much appreciated.
There may be a more elegant way, but this works:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:param name="year" select="2015"/>
<xsl:variable name="months" as="xs:date*">
<xsl:for-each select="1 to 12">
<xsl:sequence select="xs:date(concat($year, format-number(., '-00'), '-01'))"/>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/employees">
<xsl:copy>
<xsl:apply-templates select="employee"/>
</xsl:copy>
</xsl:template>
<xsl:template match="employee">
<xsl:copy>
<xsl:copy-of select="id"/>
<xsl:variable name="salary-changes" select="salaryhistory/change" />
<xsl:variable name="salaries-by-month" as="xs:integer*">
<xsl:for-each select="$months">
<xsl:sequence select="$salary-changes[xs:date(efffrom) le current()][last()]/monthlypay" />
</xsl:for-each>
</xsl:variable>
<annualsal>
<xsl:value-of select="sum($salaries-by-month)" />
</annualsal>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that salary changes are assumed to be listed in chronological order.

Grouping based on conditions and putting them into a single row in XSLT Transformation

I am facing a problem in transformation of XML. I need to transform an output in csv file as with ',' seperated.
XML:
<Root>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12345</ID>
</Employee>
<Type Descriptor="Phone"></Type>
<amount1>8</amount1>
</Employees>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12345</ID>
</Employee>
<Type Descriptor="Phone"></Type>
<amount>6</amount>
</Employees>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12345</ID>
</Employee>
<Type Descriptor="Food"></Type>
<amount>8</amount>
</Employees>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12345</ID>
</Employee>
<Type Descriptor="Travel"></Type>
<amount>8</amount>
</Employees>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12345</ID>
</Employee>
<Type Descriptor="Other"></Type>
<amount>800</amount>
</Employees>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12346</ID>
</Employee>
<Type Descriptor="Phone"></Type>
<amount>8</amount>
</Employees>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12346</ID>
</Employee>
<Type Descriptor="Phone"></Type>
<amount>8</amount>
</Employees>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12346</ID>
</Employee>
<Type Descriptor="Other"></Type>
<amount>8</amount>
</Employees>
<Employees>
<Employee>
<Co_Code>DEEP1</Co_Code>
<ID>12346</ID>
</Employee>
<Type Descriptor="Food"></Type>
<amount>8</amount>
</Employees>
</Root>
Currently using XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:param name="sep" select="', '"/>
<xsl:output method="text"/>
<xsl:template match="Root">
<xsl:value-of select="'Co Code', 'ID', 'type', 'amount'" separator="{$sep}"/>
<xsl:text>
</xsl:text>
<xsl:for-each-group select="Employees" group-adjacent="Type/#Descriptor">
<xsl:if test="position() gt 1"><xsl:text>
</xsl:text></xsl:if>
<xsl:value-of select="Employee/Co_Code, Employee/ID, current-grouping-key(), sum(current-group()/amount)"
separator="{$sep}"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Currently generating the Output in below format:
Co Code, ID, type, amount
DEEP1, 12345, Phone, 6
DEEP1, 12345, Food, 8
DEEP1, 12345, Travel, 8
DEEP1, 12345, Other, 800
DEEP1, 12346, Phone, 16
DEEP1, 12346, Other, 8
DEEP1, 12346, Food, 8
But i need to arrange the few row into column .
I am trying to get the output in below format.
Employee Sum of Phone and Food should be written in one seperate column and For other type it should create a seperate line with Phone and Food column as blank.
Co Code, ID, Phone , Food, type, amount
DEEP1, 12345, 6 , 8 , ,
DEEP1, 12345, , , Travel, 8
DEEP1, 12345, Other, 800
DEEP1, 12346, 16 , 8 , ,
DEEP1, 12346, , , Other, 8
Please give me some idea to achieve this.
Thanks to Martin Honnen for helping me.
You probably want to start off by grouping by the Employee's ID
<xsl:for-each-group select="Employees"
group-by="Employee/ID">
You then have a specific line for outputting the "Food" and "Phone" values
<xsl:value-of select="Employee/Co_Code,
current-grouping-key(),
sum(current-group()[Type/#Descriptor = 'Phone']/amount),
sum(current-group()[Type/#Descriptor = 'Food']/amount),
'',
''" separator="{$sep}"/>
And within the current group, you can then group the non "Food" and "Phone" entries by their descriptor (This would only be necessary if the descriptors can be repeated)
<xsl:for-each-group select="current-group()[not(Type/#Descriptor = ('Phone', 'Food'))]"
group-by="Type/#Descriptor">
Then it is a straight-forward case of outputting a line for this too.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:param name="sep" select="', '"/>
<xsl:output method="text"/>
<xsl:template match="Root">
<xsl:value-of select="'Co Code', 'ID', 'type', 'amount'" separator="{$sep}"/>
<xsl:text>
</xsl:text>
<xsl:for-each-group select="Employees" group-by="Employee/ID">
<xsl:value-of select="Employee/Co_Code, current-grouping-key(), sum(current-group()[Type/#Descriptor = 'Phone']/amount), sum(current-group()[Type/#Descriptor = 'Food']/amount), '', ''" separator="{$sep}"/>
<xsl:text>
</xsl:text>
<xsl:for-each-group select="current-group()[not(Type/#Descriptor = ('Phone', 'Food'))]" group-by="Type/#Descriptor">
<xsl:value-of select="Employee/Co_Code, '', '', sum(current-group()/amount), current-group()/Type/#Descriptor" separator="{$sep}"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

Copy Namespaces and elements to node using xsl

I am new to xml and xsl transformation, I have an xml file which need to be transformed by xslt mapping and my input file is like
<?xml version="1.0" encoding="UTF-8"?>
<UBLExtensions>
<Name xsi:schemaLocation="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2 ..\..\XML\Inter\Namefile_01.xsd" xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonSignatureComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:sac="urn:oasis:names:specification:ubl:schema:xsd:SignatureAggregateComponents-2" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2">
<ext:ExtensionAgencyURI>urn:invoice:hr:issueplace</ext:ExtensionAgencyURI>
<ext:ExtensionContent>
<ext:InvoiceIssuePlace>London</ext:InvoiceIssuePlace>
</ext:ExtensionContent>
</Name>
</UBLExtensions>
I want to generate an output xml with few more header fields,
the output file needs like
<?xml version="1.0" encoding="UTF-8"?>
<Header>
<details>
<author>Knorr, Stefan</author>
<title>Creepy Crawlies</title>
<genre>Horror</genre>
<price>4.95</price>
<publish_date>2000-12-06</publish_date>
<description>An anthology of horror stories about roaches,
centipedes, scorpions and other insects.</description>
</details>
<UBLExtensions>
<Name xsi:schemaLocation="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2 ..\..\XML\Inter\Namefile_01.xsd" xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonSignatureComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:sac="urn:oasis:names:specification:ubl:schema:xsd:SignatureAggregateComponents-2" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2">
<ext:ExtensionAgencyURI>urn:invoice:hr:issueplace</ext:ExtensionAgencyURI>
<ext:ExtensionContent>
<ext:InvoiceIssuePlace>London</ext:InvoiceIssuePlace>
</ext:ExtensionContent>
</Name>
</UBLExtensions>
</Header>
Use
<xsl:template match="/">
<Header>
<details>
<author>Knorr, Stefan</author>
<title>Creepy Crawlies</title>
<genre>Horror</genre>
<price>4.95</price>
<publish_date>2000-12-06</publish_date>
<description>An anthology of horror stories about roaches,
centipedes, scorpions and other insects.</description>
</details>
<xsl:copy-of select="node()"/>
</Header>
</xsl:template>