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.
Related
If I have the following xml
<root>
<house id="1">
<occupant>
</occupant>
<occupant>
</occupant>
</house>
<house id="2">
<occupant>
</occupant>
<occupant>
</occupant>
</house>
</root>
I want to count (counting is NOT the issue, the construction of the xpath is the problem, I'll append an example at the end that is more accurate but uglier to explain) the preceding 'cousins' as
I process the xslt
and the following xslt (1.0)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<counts>
<xsl:apply-templates select="root/house/occupant"/>
</counts>
</xsl:template>
<xsl:template match="occupant">
<previous>
<xsl:value-of select="count(preceding::occupant)"/>
</previous>
</xsl:template>
</xsl:stylesheet>
I get what I want
<counts>
<previous>0</previous>
<previous>1</previous>
<previous>2</previous>
<previous>3</previous>
</counts>
but this doesnt work if occupants can appear elsewhere in the xml tree e.g.
<root>
<house id="1">
<occupant>
</occupant>
<occupant>
</occupant>
<next_door>
<house id="2">
<occupant>
</occupant>
<occupant>
</occupant>
</house>
</next_door>
</house>
<house id="2">
<occupant>
</occupant>
<occupant>
</occupant>
</house>
</root>
I'm not interested in 'next_door', in fact I'm ONLY interested in 'cousins' (and siblings) in the tree, i.e. things on the path 'root/house/occupant'
the above will count any occupant, and preceding sibling will only count, quite sensibly, siblings.
I feel that I want to count
/root/house/occupant[some predicate that says this node precedes the current one]
P.S. The actual issue is more like this, i.e. extracting data from specific cousins (but explaining the output is quite convoluted, where counts are nice and easy to explain)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<counts>
<xsl:apply-templates select="root/house/occupant"/>
</counts>
</xsl:template>
<xsl:template match="occupant">
<previous_and_next>
<previous>
<xsl:copy-of select="(preceding::occupant)[1]"/>
</previous>
<next>
<xsl:copy-of select="(following::occupant)[1]"/>
</next>
</previous_and_next>
</xsl:template>
</xsl:stylesheet>
I would use xsl:number e.g.
<previous>
<xsl:number count="root/house/occupant" level="any"/>
</previous>
As you want to substract one e.g.
<previous>
<xsl:variable name="count"><xsl:number count="root/house/occupant" level="any"/></xsl:variable>
<xsl:value-of select="$count - 1"/>
</previous>
To select with set "except" operations based on node-count in XPath 1.0 you can use
<xsl:template match="occupant">
<previous>
<xsl:variable name="self-and-following" select=". | following::occupant"/>
<xsl:variable name="cousins" select="/root/house/occupant[count(. | $self-and-following) = count($self-and-following) + 1]"/>
<xsl:value-of select="count($cousins)"/>
</previous>
</xsl:template>
It's not clear what exactly you want. From the context of occupant on the path of /root/house/occupant you can select the preceding occupants on the same path using:
preceding-sibling::occupant | ../preceding-sibling::house/occupant"
I am new to XSL. Hence please help me with the below.
I have 2 xmls. I have to do the following in XSL transformation.
if Employee/EmployeeInfo/FirstName = EmployeeSegment/EmployeeSummary/GivenName and Employee/EmployeeInfo/LastName = EmployeeSegment/EmployeeSummary/Surname
employeeId = EmployeeSegment/EmployeeSummary/EmpId
XML1
<Employee>
<EmployeeInfo>
<FirstName>ABC</FirstName>
<LastName>DEF</LastName>
</EmployeeInfo>
</Employee>
XML2
<EmployeeSegment>
<EmployeeSummary>
<EmpId>1234</EmpId>
<GivenName>ABC</GivenName>
<Surname>DEF</Surname>
</EmployeeSummary>
</EmployeeSegment>
I have tried the following. It is not working.
<xsl:param name="cjEmployeeSegment" select="document('CJ_Response.xml')"/>
<xsl:for-each select="/ns3:Employee/ns3:EmployeeInfo">
<xsl:variable name="empFirstName">
<xsl:value-of select="ns1:FirstName"/>
</xsl:variable>
<xsl:variable name="empLastName">
<xsl:value-of select="ns1:LastName"/>
</xsl:variable>
<xsl:for-each select="$cjEmployeeSegment/v32:EmployeeSegment/v31:EmployeeSummary">
<xsl:if test="$empFirstName=v31:GivenName and $empLastName=v31:Surname">
<ns12:EmployeeIdentifier>
<ns12:EmployeeID>
<xsl:value-of select="v31:EmpId"/>
</ns12:EmployeeID>
</ns12:EmployeeIdentifier>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
Assuming you are processing the following input:
XML
<Employee>
<EmployeeInfo>
<FirstName>ABC</FirstName>
<LastName>DEF</LastName>
</EmployeeInfo>
</Employee>
and there is another XML document named CJ_Response.xml:
<EmployeeSegment>
<EmployeeSummary>
<EmpId>1234</EmpId>
<GivenName>ABC</GivenName>
<Surname>DEF</Surname>
</EmployeeSummary>
</EmployeeSegment>
you can use the following stylesheet:
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:param name="cj_Response" select="document('CJ_Response.xml')"/>
<xsl:template match="/Employee">
<root>
<xsl:for-each select="EmployeeInfo">
<xsl:variable name="lookup" select="$cj_Response/EmployeeSegment/EmployeeSummary[GivenName = current()/FirstName and Surname = current()/LastName]" />
<xsl:if test="$lookup">
<EmployeeIdentifier>
<EmployeeID>
<xsl:value-of select="$lookup/EmpId"/>
</EmployeeID>
</EmployeeIdentifier>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
to return:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<EmployeeIdentifier>
<EmployeeID>1234</EmployeeID>
</EmployeeIdentifier>
</root>
Of course, this will fail miserably if there are two or more employees with the same name.
Say I have a simple input XML document like:
<Amounts>
<item>
<Base>4750</Base>
<Tax1>2800</Tax1>
<Tax2>50</Tax2>
</item>
<item>
<Base>4750</Base>
<Tax1>2800</Tax1>
<Tax2>50</Tax2>
</item>
</Amounts>
And an XSLT as:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output version="1.0" method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:element name="MyTax">
<xsl:variable name="bf" select="sum(//item/Base)"/>
<xsl:variable name="discount" select="$bf * 0.08"/>
<xsl:variable name="bf1" select="$bf - $discount"/>
<xsl:variable name="yq1" select="sum(//item/Tax1)"/>
<xsl:variable name="yr1" select="sum(//item/Tax2)"/>
<xsl:variable name="stax">
<xsl:value-of select="round(format-number((($bf1 + $yq1 + $yr1) * 0.0495),'#.##'))"/>
</xsl:variable>
<xsl:element name="Stax"><xsl:value-of select="$stax"/></xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
We get an output XML as:
<MyTax>
<Stax>715</Stax>
</MyTax>
However, now i'd like to compute stax separately for each item and then sum those items up - this is because in the above example I end up with rounding errors as the sum of the individual stax is actually 714 - the value I am looking for, rather than 715 above.
So the output I am looking for would be:
<MyTax>
<item>
<Stax>357</Stax>
</item>
<item>
<Stax>357</Stax>
</item>
<TotalStax>714</TotalStax>
</MyTax>
Though I can set up a for-each, how do I compute stax into a variable for each pass, and then sum up the individual stax? I am limited XSLT 1.0, saw some answers using keys and nodesets but am not able to understand how to apply them.
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:variable name="vrtfTaxSeq">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="sum(ext:node-set($vrtfTaxSeq)/*)"/>
</xsl:template>
<xsl:template match="item">
<ttax>
<xsl:value-of select="round(0.0495*(Base - 0.08*Base + Tax1 + Tax2))"/>
</ttax>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Amounts>
<item>
<Base>4750</Base>
<Tax1>2800</Tax1>
<Tax2>50</Tax2>
</item>
<item>
<Base>4750</Base>
<Tax1>2800</Tax1>
<Tax2>50</Tax2>
</item>
</Amounts>
calculates the sums for each item and produces the wanted result:
714
Do note that the xxx:node-set() extension function is necessary (this can be avoided using recursion).
UPDATE: Cannot use any EXSLT extensions.
Also I'm using date in two different places and I only want to update one of them and not both.
I need to increment a date in my XSLT transformation. I'm using XSLT 1.0.
In source XML I have a date like this
<XML>
<Date>4/22/2011 3:30:43 PM</Date>
</XML>
Then I need to add 10 years to the output. Like this
<Output>
<Odate>4/22/2011 3:30:43 PM</Odate>
<Cdate>4/22/2021 3:30:43 PM</Cdate>
</Output>
How this can be done in XSLT 1.0. Thanks in advance.
The following is no general date arithmetic implementation but might suffice to increment the year part:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="year-inc" select="10"/>
<xsl:template match="XML">
<Output>
<xsl:apply-templates/>
</Output>
</xsl:template>
<xsl:template match="Date">
<xsl:variable name="d0" select="substring-before(., '/')"/>
<xsl:variable name="d1" select="substring-before(substring-after(., '/'), '/')"/>
<xsl:variable name="d2" select="substring-after(substring-after(., '/'), '/')"/>
<xsl:variable name="new-year" select="substring($d2, 1, 4) + $year-inc"/>
<Cdate>
<xsl:value-of select="concat($d0, '/', $d1, '/', $new-year, substring($d2, 5))"/>
</Cdate>
</xsl:template>
</xsl:stylesheet>
Depends a little how pernickety you want to be, e.g. what's the date 10 years after 29 Feb 2004? There are a number of useful XSLT 1.0 date-handling routines you can download at www.exslt.org, I think they include both a parse-date template which will convert your US-format date into a standard ISO date, date arithmetic templates which will allow you to add a duration to an ISO-format date, and a format-date function that will turn it back into US format.
I have figured it with the help of #Martin. I extend on #Martin's code and only called the template when I need to modify the date.
XSLT:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="year-inc" select="10"/>
<xsl:template match="XML">
<Output>
<Odate>
<xsl:value-of select="Date"/>
</Odate>
<Cdate>
<xsl:call-template name="increment"/>
</Cdate>
</Output>
</xsl:template>
<xsl:template name="increment">
<xsl:variable name="d0" select="substring-before(Date, '/')"/>
<xsl:variable name="d1" select="substring-before(substring-after(Date, '/'), '/')"/>
<xsl:variable name="d2" select="substring-after(substring-after(Date, '/'), '/')"/>
<xsl:variable name="new-year" select="substring($d2, 1, 4) + $year-inc"/>
<xsl:value-of select="concat($d0, '/', $d1, '/', $new-year, substring($d2, 5))"/>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<Output>
<Odate>4/22/2011 3:30:43 PM</Odate>
<Cdate>4/22/2021 3:30:43 PM</Cdate>
</Output>
<?xml version="1.0"?>
<Products>
<product>
<productId >1</productId>
<textdate>11/11/2011</textdate>
<price>200</price>
</product>
<product>
<productId >6</productId>
<textdate>11/11/2011</textdate>
<price>100</price>
</product>
<product>
<productId >1</productId>
<textdate>16/11/2011</textdate>
<price>290</price>
</product>
</Products>
I've this xml and I want an xslt transformation that regroup product something like this :
{ product 1 :
11/11/2011 - 200
16/11/2011 - 290 }
{ product 6
11/11/2011 - 100 }
I work with xslt 1.0 Asp .net C# XslCompiledTransformation
Use Muenchian grouping as explained here: http://www.jenitennison.com/xslt/grouping/index.xml. If you need help with writing the code then please state whether you want plain text output or HTML output in the format you posted.
This XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="groupById" match="product" use="productId"/>
<xsl:template match="/*">
<xsl:apply-templates select="product[
generate-id() =
generate-id( key( 'groupById', productId ) )
]"/>
</xsl:template>
<xsl:template match="product">
<xsl:text>{ product </xsl:text>
<xsl:value-of select="concat(productId, ' :
')"/>
<xsl:apply-templates select="key( 'groupById', productId )" mode="inner-content"/>
<xsl:text> }
</xsl:text>
</xsl:template>
<xsl:template match="product" mode="inner-content">
<xsl:value-of select="concat( textdate, ' - ', price )"/>
<xsl:if test="position() != last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Applied to your code sample, it will produce this result:
{ product 1 :
11/11/2011 - 200
16/11/2011 - 290 }
{ product 6 :
11/11/2011 - 100 }