XML to CSV (Generation of New Date with input Date) - xslt-2.0

I have a requirement in which I have to generate new date(YYYYMMDD) from the date which is coming in the record(YYYYMMDD) and below is logic for New date creation:
1.If 'MM - 1' = 0, Then make MM -'12', and Year value as YearFromActualDate - 1
2.DD is always '01'
3.If 'MM - 1' != 0, Then MM in output will be 'MM - 1' and Year will remain the same
I have mentioned the Expected Output which will give idea of above mentioned logic.
Please advise.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions" >
<xsl:output method="text" encoding="utf-8" />
<xsl:param name="delim" select="','" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:value-of select="$quote" />
<xsl:text>Name</xsl:text>
<xsl:value-of select="$quote" />
<xsl:value-of select="$delim" />
<xsl:value-of select="$quote" />
<xsl:text>Date</xsl:text>
<xsl:value-of select="$quote" />
<xsl:value-of select="$delim" />
<xsl:value-of select="$quote" />
<xsl:text>NewDate</xsl:text>
<xsl:value-of select="$quote" />
<xsl:value-of select="$break" />
<xsl:apply-templates select="ID/ED/E1" />
</xsl:template>
<xsl:template match="E1">
<xsl:value-of select="$quote" />
<xsl:value-of select="name"/>
<xsl:value-of select="$quote" />
<xsl:value-of select="$delim" />
<xsl:value-of select="$quote" />
<xsl:value-of select="date"/>
<xsl:value-of select="$quote" />
<xsl:value-of select="$delim" />
<xsl:value-of select="$quote" />
<xsl:variable name="newdate" select="'01'" />
<xsl:variable name="inMonth" select="substring(date,5,2)" />
<xsl:variable name="inputYear" select="substring(date,1,4)" />
<xsl:choose>
<xsl:when test = "$inMonth='01'">
<xsl:variable name="calculatedMonth" select="12"/>
<xsl:value-of select="concat(xs:integer($inputYear) - 1,$calculatedMonth,$newdate)" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="inMonthlength" select="string-length(xs:string(xs:integer($inMonth) - 1))" />
<xsl:if test="xs:integer($inMonthlength) !=2">
<xsl:value-of select="concat($inputYear,concat(0,xs:integer($inMonth) - 1),$newdate)" />
</xsl:if>
<xsl:if test="xs:integer($inMonthlength) =2">
<xsl:value-of select="concat($inputYear,(xs:integer($inMonth) - 1),$newdate)" />
</xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$quote"/>
<xsl:if test="following-sibling::*">
<xsl:value-of select="$break" />
</xsl:if>
</xsl:template>
Input:
<ID>
<ED>
<E1>
<name>Eva</name>
<date>20190504</date> (Consider date as YYYYMMDD)
</E1>
<E1>
<name>Alan</name>
<date>20190101</date>
</E1>
</ED>
<ID>
Expected Output:
"Name","Date","NewDate"
"Eva","20190504","20190401"
"Alan","20190101","20181201"

If I've reverse-engineered your algorithm correctly, what you want is the first day of the previous month.
Logically the steps are:
Convert your date to an xs:date value
Take the first day of the current month
Subtract one month.
In practice it's simplest to combine (1) and (2) so you end up with
xs:date(replace($date, '(....)(..)(..)', '$1-$2-01')) - xs:yearMonthDuration('P1M')
and then you can format this date as YYYYMMDD using
format-date($date, '[Y0001][M01][D01]')

Related

Need help in grouping and transposing data using XSLT

I need help in conditional grouping and transposing XML data.
I need to group and transpose data using week of the month, Worker ID and Rate_Category_Code. Need to report the Hours_Worked in the appropriate weeks. Hours would be blank for all other week. Data in XML will be never for more then a month.
If there are overtime hours reported (Identified by Rate_Category_Code as OT) then i need to create separate row for the week in which overtime hours was reported.
Below is the XML data -
``
<?xml version="1.0" encoding="UTF-8"?>
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT1120_CR_HR_Fieldglass_Daily_Timesheet">
<wd:Report_Entry>
<wd:Worker_ID>FNRAWK00001743</wd:Worker_ID>
<wd:Task_Code>01</wd:Task_Code>
<wd:GL_Account_Code>Default</wd:GL_Account_Code>
<wd:Rate_Category_Code>ST</wd:Rate_Category_Code>
<wd:UOM>HR</wd:UOM>
<wd:Hours_Worked>8</wd:Hours_Worked>
<wd:Reported_Date>2019-06-10</wd:Reported_Date>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_ID>FNRAWK00001743</wd:Worker_ID>
<wd:Task_Code>01</wd:Task_Code>
<wd:GL_Account_Code>Default</wd:GL_Account_Code>
<wd:Rate_Category_Code>ST</wd:Rate_Category_Code>
<wd:UOM>HR</wd:UOM>
<wd:Hours_Worked>8</wd:Hours_Worked>
<wd:Reported_Date>2019-06-18</wd:Reported_Date>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_ID>FNRAWK00001743</wd:Worker_ID>
<wd:Task_Code>01</wd:Task_Code>
<wd:GL_Account_Code>Default</wd:GL_Account_Code>
<wd:Rate_Category_Code>OT</wd:Rate_Category_Code>
<wd:UOM>HR</wd:UOM>
<wd:Hours_Worked>1</wd:Hours_Worked>
<wd:Reported_Date>2019-06-10</wd:Reported_Date>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_ID>FNRAWK00001742</wd:Worker_ID>
<wd:Task_Code>01</wd:Task_Code>
<wd:GL_Account_Code>Default</wd:GL_Account_Code>
<wd:Rate_Category_Code>ST</wd:Rate_Category_Code>
<wd:UOM>HR</wd:UOM>
<wd:Hours_Worked>9</wd:Hours_Worked>
<wd:Reported_Date>2019-06-20</wd:Reported_Date>
</wd:Report_Entry>
</wd:Report_Data>
``
Desired output -
``
Type=Upload Full Time Sheet with Revision
Transaction=True
Approval Required=True
Messaging Required=False
Language=English (United States)
Number Format=#,##9.99 (Example: 1,234,567.99)
Date Format=MM/DD/YYYY
Submit=FALSE
Buyer=FNRA
Supplier Review=False
Comments=
Worker_ID|Month_Start_Date|First_Monday|Cost_Center_Code|Task_Code|GL_Account_Code|Rate_Category_Code|UOM|Mon_Hrs|Tue_Hrs|Wed_Hrs|Thu_Hrs|Fri_Hrs|Sat_Hrs|Sun_Hrs
FNRAWK00001743|06/01/2019|05/27/2019||01|Default|ST|HR||||||||
FNRAWK00001743|06/01/2019|06/03/2019||01|Default|ST|HR||||||||
FNRAWK00001743|06/01/2019|06/10/2019||01|Default|ST|HR|8|||||||
FNRAWK00001743|06/01/2019|06/10/2019||01|Default|OT|HR|1|||||||
FNRAWK00001743|06/01/2019|06/17/2019||01|Default|ST|HR||8||||||
FNRAWK00001743|06/01/2019|06/24/2019||01|Default|ST|HR||||||||
FNRAWK00001742|06/01/2019|05/27/2019||01|Default|ST|HR||||||||
FNRAWK00001742|06/01/2019|06/03/2019||01|Default|ST|HR||||||||
FNRAWK00001742|06/01/2019|06/10/2019||01|Default|ST|HR||||||||
FNRAWK00001742|06/01/2019|06/10/2019||01|Default|OT|HR||||||||
FNRAWK00001742|06/01/2019|06/17/2019||01|Default|ST|HR||||9||||
FNRAWK00001742|06/01/2019|06/24/2019||01|Default|ST|HR||||||||
``
XSLT -
``
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wd="urn:com.workday.report/INT1120_CR_HR_Fieldglass_Daily_Timesheet" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:my="http://www.example.com/my" xmlns:functx="http://www.functx.com" exclude-result-prefixes="xs my functx">
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:variable name="Linefeed" select="'
'"/>
<xsl:variable name="Delimiter" select=" '|' "/>
<xsl:function name="functx:repeat-string" as="xs:string">
<xsl:param name="stringToRepeat" as="xs:string?"/>
<xsl:param name="count" as="xs:integer"/>
<xsl:sequence select="string-join((for $i in 1 to $count return $stringToRepeat),'')"/>
</xsl:function>
<xsl:function name="functx:pad-integer-to-length" as="xs:string">
<xsl:param name="integerToPad" as="xs:anyAtomicType?"/>
<xsl:param name="length" as="xs:integer"/>
<xsl:sequence select="if ($length < string-length(string($integerToPad))) then error(xs:QName('functx:Integer_Longer_Than_Length')) else concat (functx:repeat-string('0',$length - string-length(string($integerToPad))),string($integerToPad))"/>
</xsl:function>
<xsl:function name="functx:date" as="xs:date">
<xsl:param name="year" as="xs:anyAtomicType"/>
<xsl:param name="month" as="xs:anyAtomicType"/>
<xsl:param name="day" as="xs:anyAtomicType"/>
<xsl:sequence select="xs:date(concat(functx:pad-integer-to-length(xs:integer($year),4),'-',functx:pad-integer-to-length(xs:integer($month),2),'-',functx:pad-integer-to-length(xs:integer($day),2)))"/>
</xsl:function>
<!--Function which accepts date as input and returns the date for first day of the month (Input Date)-->
<xsl:function name="functx:first-day-of-month" as="xs:date?">
<xsl:param name="date" as="xs:anyAtomicType?"/>
<xsl:sequence select="functx:date(year-from-date(xs:date($date)),month-from-date(xs:date($date)),1)"/>
</xsl:function>
<!--Function which accepts date as input and returns the date for first day of the week-->
<xsl:function name="my:thisMonday">
<xsl:param name="date"/>
<xsl:variable name="epoch" select="xs:date('0001-01-01')"/>
<xsl:variable name="dayNumber" select="fn:days-from-duration($date - $epoch)"/>
<xsl:variable name="dayOfWeek" select="$dayNumber mod 7"/>
<xsl:value-of select="$date - xs:dayTimeDuration(concat('P', $dayOfWeek, 'D' ))"/>
</xsl:function>
<xsl:template match="wd:Report_Data">
<File>
<Header>
<xsl:text>Type=Upload Full Time Sheet with Revision</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Transaction=True</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Approval Required=True</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Messaging Required=False</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Language=English (United States)</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Number Format=#,##9.99 (Example: 1,234,567.99)</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Date Format=MM/DD/YYYY</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Submit=FALSE</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Buyer=FNRA</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Supplier Review=False</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Comments=</xsl:text>
<xsl:value-of select="$Linefeed"/>
</Header>
<xsl:value-of select="$Linefeed"/>
<Header>
<xsl:text>Worker_ID|Date|Week_Start_Date|Cost_Center_Code|Task_Code|GL_Account_Code|Rate_Category_Code|UOM|Mon_Hrs|Tue_Hrs|Wed_Hrs|Thu_Hrs|Fri_Hrs|Sat_Hrs|Sun_Hrs</xsl:text>
<xsl:value-of select="$Linefeed"/>
</Header>
<xsl:for-each select="wd:Report_Entry">
<xsl:call-template name="Write_Rows"/>
</xsl:for-each>
</File>
</xsl:template>
<xsl:template name="Write_Rows">
<Record>
<Worker_ID>
<xsl:value-of select="wd:Worker_ID"/>
</Worker_ID>
<xsl:value-of select="$Delimiter"/>
<Date>
<!--<xsl:value-of select="concat(substring(wd:Date,6, 2),'/',substring(wd:Date, 9, 2),'/',substring(wd:Date, 1, 4))"/>-->
<!--Pass date in YYYY-MM-DD format to custom function and format the return value to 'MM/DD/YYYY'-->
<xsl:variable name="StartDateOfMonth" select="string(functx:first-day-of-month(xs:date(concat(substring(wd:Reported_Date, 1, 4),'-', substring(wd:Reported_Date,6, 2),'-',substring(wd:Reported_Date, 9, 2)))))"/>
<xsl:value-of select="concat(substring($StartDateOfMonth,6,2),'/',substring($StartDateOfMonth,9,2),'/',substring($StartDateOfMonth,1,4))"/>
</Date>
<xsl:value-of select="$Delimiter"/>
<Week_Start_Date>
<!--<xsl:variable name="WeekStartDate" select="my:thisMonday(xs:date('2019-06-01'))"/>-->
<!--Format the return value to 'MM/DD/YYYY'-->
<xsl:variable name="WeekStartDate" select="my:thisMonday(xs:date(wd:Reported_Date))"/>
<xsl:value-of select="concat(substring($WeekStartDate,6,2),'/',substring($WeekStartDate,9,2),'/',substring($WeekStartDate,1,4))"/>
</Week_Start_Date>
<xsl:value-of select="$Delimiter"/>
<Cost_Center_Code>
<xsl:value-of select="wd:Cost_Center_Code"/>
</Cost_Center_Code>
<xsl:value-of select="$Delimiter"/>
<Task_Code>
<xsl:value-of select="wd:Task_Code"/>
</Task_Code>
<xsl:value-of select="$Delimiter"/>
<GL_Account_Code>
<xsl:value-of select="wd:GL_Account_Code"/>
</GL_Account_Code>
<xsl:value-of select="$Delimiter"/>
<Rate_Category_Code>
<xsl:value-of select="wd:Rate_Category_Code"/>
</Rate_Category_Code>
<xsl:value-of select="$Delimiter"/>
<UOM>
<xsl:value-of select="wd:UOM"/>
</UOM>
<xsl:value-of select="$Delimiter"/>
<Hours_Worked>
<xsl:value-of select="wd:Hours_Worked"/>
</Hours_Worked>
<xsl:value-of select="$Delimiter"/>
<Overtime>
<xsl:value-of select="wd:Overtime"/>
</Overtime>
<xsl:value-of select="$Linefeed"/>
</Record>
</xsl:template>
</xsl:stylesheet>
``
I think part of the task is grouping with a composite key e.g. in XSLT 3:
<xsl:for-each-group select="wd:Report_Entry" composite="yes" group-by="wd:Worker_ID, wd:Rate_Category_Code, my:thisMonday(xs:date(wd:Reported_Date))">
<xsl:value-of select="current-grouping-key(), sum(current-group()/wd:Hours_Worked)" separator="|"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
That gives the existing data:
FNRAWK00001743|ST|2019-06-10|8
FNRAWK00001743|ST|2019-06-17|8
FNRAWK00001743|OT|2019-06-10|1
FNRAWK00001742|ST|2019-06-17|9
You will need to add logic for outputting empty lines for weeks for which there is no data.

How to access variable defined under for-each loop/If condition in another for each loop

I have to generate the output in sequence and so I wanted to know how to access the variable defined under For-each loop/If condition and then value of select inside another for loop.
As per my example how to access partn and date3? Please help and suggest.
what is the concept for achieving the same..I have tried with-param as well, but didn't work for me.
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="myfunc"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions" >
<xsl:output method="text" encoding="utf-8" />
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:template match="ZGS/ID">
<xsl:for-each select="E1">
<xsl:if test="PA = 'CE'">
<xsl:variable name="partn" select="PAN"/>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="E13">
<xsl:if test="ID = 033">
<xsl:variable name="date3"
select="substring(DAT,3,8)"/>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="E1E">
<xsl:text>823</xsl:text>
<xsl:text>03</xsl:text>
<xsl:for-each select="E1ED">
<xsl:if test="QU = 012 ">
<xsl:value-of select="BEL"/>
</xsl:if>
</xsl:for-each>
<xsl:value-of select="$partn"/>
<xsl:value-of select="$date3"/>
</xsl:for-each>
</xsl:template>
INPUT:
<?xml version='1.0' encoding='utf-8'?>
<ZGS>
<ID BEGIN="1">
<E1 SEGMENT="1">
<PA>AG</PA>
<NAME>ABC</NAME>
<SP>E</SP>
<AND>0004</AND>
</E1>
<E1 SEGMENT="1">
<PA>RE</PA>
<PAN>IUIOP</PAN>
<NAME>ABC1</NAME>
<SP>EQ</SP>
<AND>0005</AND>
<EKA3 SEGMENT="1">
<QU>009</QU>
</EKA3>
</E1>
<E1 SEGMENT="1">
<PA>CE</PA>
<PAN>PODW</PAN>
<NAME>ABC2</NAME>
<SP>EP</SP>
<AND>0006</AND>
</E1>
<E13 SEGMENT="1">
<ID>001</ID>
<DAT>20190329</DAT>
</E13>
<E13 SEGMENT="1">
<ID>002</ID>
<DAT>20190429</DAT>
</E13>
<E13 SEGMENT="1">
<IDD>033</IDD>
<DAT>20190529</DAT>
</E13>
<E1E>
<E1ED>
</E1ED>
<E1ED>
</E1ED>
</E1E>
In XSLT, variables once declared/defined, they cannot be changed. And exists only in the loop they are defined.
You might not need the xsl:for-each loop here. Instead the variables can be globally defined, so that you can use them where you want in your xslt.
You can try the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="myfunc"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="text" encoding="utf-8" />
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:variable name="partn" select="/ZGS/ID/E1[PA = 'CE']/PAN" />
<xsl:variable name="date3" select="substring(/ZGS/ID/E13[ID = '033']/DAT,3,8)" />
<xsl:template match="ZGS/ID">
<xsl:for-each select="E1E">
<xsl:text>823</xsl:text>
<xsl:text>03</xsl:text>
<xsl:for-each select="E1ED">
<xsl:if test="QU = 012 ">
<xsl:value-of select="BEL" />
</xsl:if>
</xsl:for-each>
<xsl:value-of select="$partn" />
<xsl:value-of select="$date3" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYiu
Using xsl:param, it can be achieved as
<xsl:param name="partn" select="/ZGS/ID/E1[PA = 'CE']/PAN" />
<xsl:param name="date3" select="substring(/ZGS/ID/E13[ID = '033']/DAT,3,8)" />
<xsl:template match="ZGS/ID">
<xsl:for-each select="E1E">
<xsl:text>823</xsl:text>
<xsl:text>03</xsl:text>
<xsl:for-each select="E1ED">
<xsl:if test="QU = 012 ">
<xsl:value-of select="BEL" />
</xsl:if>
</xsl:for-each>
<xsl:for-each select="$partn">
<xsl:value-of select="." />
</xsl:for-each>
<xsl:value-of select="$date3" />
</xsl:for-each>
</xsl:template>
https://xsltfiddle.liberty-development.net/jyRYYiu/1

Subtraction by decimal number

I have a to subtract the amount -1 based on the condition.Please any one help.
Input:
<JD>
<GP xmlns="">
I xmlns="">
<PK>40</PK>
<A/>
<AMNT>11659650.15</AMNT>
<B/>
<C/>
</I>
<I xmlns="">
<PK>50</PK>
<A/>
<AMNT>11659650.15</AMNT>
<B/>
<C/>
</I>
</GP>
</JD>
Tried with below XSLT and got 1.165964915E7 for 50.
<xsl:for-each select="JD/mo:GP/I">
<xsl:if test="PK='40'">
<xsl:variable name="a" select="AMNT"/>
<xsl:element name="AMT">
<xsl:value-of select="$a"/>
</xsl:element>
</xsl:if>
<xsl:if test="PK='50'">
<xsl:variable name="a" select="AMNT"/>
<xsl:element name="AMT">
<xsl:value-of select="$a - 1"/>
</xsl:element>
</xsl:if>
Considering your given input as following:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<JD>
<GP>
<I>
<PK>40</PK>
<A />
<AMNT>11659650.15</AMNT>
<B />
<C />
</I>
<I>
<PK>50</PK>
<A />
<AMNT>11659650.15</AMNT>
<B />
<C />
</I>
</GP>
</JD>
</Root>
In XSLT 2.0, you can try it using xs:decimal as below:
<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 omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/Root">
<xsl:for-each select="JD/mo:GP/I">
<xsl:if test="PK='40'">
<xsl:variable name="a" select="AMNT" />
<xsl:element name="AMT">
<xsl:value-of select="$a" />
</xsl:element>
</xsl:if>
<xsl:if test="PK='50'">
<xsl:variable name="a" select="AMNT" />
<xsl:element name="AMT">
<xsl:value-of select="xs:decimal($a) - 1" />
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
In XSLT 1.0, Use the format-number() function:
<xsl:template match="/Root">
<xsl:for-each select="JD/mo:GP/I">
<xsl:if test="PK='40'">
<xsl:variable name="a" select="AMNT" />
<xsl:element name="AMT">
<xsl:value-of select="$a" />
</xsl:element>
</xsl:if>
<xsl:if test="PK='50'">
<xsl:variable name="a" select="AMNT" />
<xsl:element name="AMT">
<xsl:value-of select="format-number($a - 1, '0.##')" />
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>

Aggregate nodes based on conditon under new parent

I have an given XML and I want to convert it in new xml and want to aggregate nodes based on status and orderId.
<?xml version="1.0" encoding="utf-8"?>
<OrderStatusUpdate>
<OrderStatusEvents>
<OrderStatusEvent>
<StoreCode>store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item1</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:37:05</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item2</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item3</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Shipped</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order2</OrderId>
<ItemId>Item1</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
</OrderStatusEvents>
</OrderStatusUpdate>
And I want an output like this. Here I am grouping elements based on status and orderId.
<Orders>
<group name="CANCELLED">
<STATUS ID="CANCELLED" DESCRIPTION="Goods Cancelled">
<ORDER ID="Order1">
<ORDER_ITEM item="item1" />
<ORDER_ITEM item="item2" />
</ORDER>
</STATUS>
<STATUS ID="CANCELLED" DESCRIPTION="Goods Cancelled">
<ORDER ID="Order2">
<ORDER_ITEM item="item1" />
</ORDER>
</STATUS>
</group>
<group name="SHIPPED">
<STATUS ID="SHIPPED" DESCRIPTION="Goods SHIPPED">
<ORDER ID="Order1">
<ORDER_ITEM item="item3" />
</ORDER>
</STATUS>
<group>
</Orders>
I am using the following xslt and it is working fine. Is there any way to improve this.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="OrderStatusUpdate/OrderStatusEvents">
<Orders>
<xsl:for-each-group select="OrderStatusEvent" group-by="OrderStatusDetails/OrderStatusDetail/StatusName">
<xsl:variable name="group-name" select="current-grouping-key()" />
<group name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="OrderId">
<xsl:variable name="order-id" select="current-grouping-key()" />
<xsl:element name="STATUS">
<xsl:attribute name="ID"><xsl:value-of select="$group-name" /></xsl:attribute>
<xsl:attribute name="DESCRIPTION">Goods <xsl:value-of select="$group-name" /></xsl:attribute>
<xsl:element name="ORDER">
<xsl:attribute name="ID"><xsl:value-of select="$order-id" /></xsl:attribute>
<xsl:for-each select="current-group()">
<xsl:if test="$group-name = 'Shipped'">
<xsl:call-template name="Shipped">
<xsl:with-param name="nodes">
<xsl:copy-of select="." />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:if test="$group-name = 'Cancelled'">
<xsl:call-template name="Cancelled">
<xsl:with-param name="nodes">
<xsl:copy-of select="." />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:for-each-group>
</group>
</xsl:for-each-group>
</Orders>
</xsl:template>
<xsl:template name="Shipped">
<xsl:param name="nodes">
</xsl:param>
<xsl:element name="ORDER_ITEM">
<xsl:attribute name="ID"><xsl:value-of select="$nodes/OrderStatusEvent/ItemId" /></xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template name="Cancelled">
<xsl:param name="nodes"></xsl:param>
<xsl:element name="ORDER_ITEM">
<xsl:attribute name="ID"><xsl:value-of select="$nodes/OrderStatusEvent/ItemId" /></xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It is easier to use literal result elements and attribute value templates as long as you don't need to compute element or attribute names at run-time and I don't think you need the two templates and call-template, it suffices to use
<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:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="OrderStatusUpdate/OrderStatusEvents">
<Orders>
<xsl:for-each-group select="OrderStatusEvent" group-by="OrderStatusDetails/OrderStatusDetail/StatusName">
<xsl:variable name="group-name" select="current-grouping-key()" />
<group name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="OrderId">
<xsl:variable name="order-id" select="current-grouping-key()" />
<STATUS ID="{$group-name}" DESCRIPTION="Goods {$group-name}">
<ORDER ID="{$order-id}">
<xsl:apply-templates select="current-group()"/>
</ORDER>
</STATUS>
</xsl:for-each-group>
</group>
</xsl:for-each-group>
</Orders>
</xsl:template>
<xsl:template match="OrderStatusEvent">
<ORDER_ITEM ID="{ItemId}"/>
</xsl:template>
</xsl:stylesheet>

Xslt for splitting a url in parts and combing them again

I want to build a sort of breadcrumb. I have link for example
http://server/site1/site2/site3
and want to build something like
http://serverhttp://server/site1...
How can I do this with xslt?
This can be accomplished with a recursive template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="urlSample" select="'http://server/site1/site2/site3'" />
<xsl:template match="/">
<xsl:call-template name="UrlLinks">
<xsl:with-param name="url" select="$urlSample" />
</xsl:call-template>
</xsl:template>
<xsl:template name="UrlLinks">
<xsl:param name="url" />
<xsl:call-template name="UrlLinksIter">
<xsl:with-param name="portionSoFar" select="concat(substring-before($url, '://'), '://')" />
<xsl:with-param name="remainder" select="concat(substring-after($url, '://'), '/')" />
</xsl:call-template>
</xsl:template>
<xsl:template name="UrlLinksIter">
<xsl:param name="portionSoFar" />
<xsl:param name="remainder" />
<xsl:variable name="nextPart" select="substring-before($remainder, '/')" />
<xsl:variable name="nextRemainder" select="substring-after($remainder, '/')" />
<xsl:if test="normalize-space($nextRemainder) or normalize-space($nextPart)">
<xsl:variable name="url" select="concat($portionSoFar, $nextPart)"/>
<xsl:if test="normalize-space($nextPart)">
<!-- $nextPart could be empty if there are multiple slashes in a row-->
<a href="{$url}">
<xsl:value-of select="$url"/>
</a>
</xsl:if>
<xsl:call-template name="UrlLinksIter">
<xsl:with-param name="portionSoFar" select="concat($url, '/')" />
<xsl:with-param name="remainder" select="$nextRemainder" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this is run on any input (since the sample value is in a variable here) this produces:
http://server
http://server/site1
http://server/site1/site2
http://server/site1/site2/site3

Resources