XSLT Help - Compare preceding node and counter increment if the date is different - xslt

I have xml where each worker can have multiple events. The events need not be grouped but if there are two events with same Effective_Date, I'd like to increment the sequence. Below is sample xml:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<wd:Report_Data xmlns:wd="urn:com.workday.report/ERP-HCM-CR_ASSIGNMENTS_REPORT_V4">
<wd:Report_Entry>
<wd:Worker_group>
<wd:UNIQUE_IDENTIFIER>2168636</wd:UNIQUE_IDENTIFIER>
</wd:Worker_group>
<wd:Caregiver_Events_group>
<wd:ASSIGNMENT_NUMBER>2168636-03012008E01</wd:ASSIGNMENT_NUMBER>
<wd:EFFECTIVE_DATE>2008-03-01-08:00</wd:EFFECTIVE_DATE>
<wd:EFFECTIVE_SEQUENCE>1</wd:EFFECTIVE_SEQUENCE>
</wd:Caregiver_Events_group>
<wd:Caregiver_Events_group>
<wd:ASSIGNMENT_NUMBER>2168636-03012008E01</wd:ASSIGNMENT_NUMBER>
<wd:EFFECTIVE_DATE>2019-12-01-08:00</wd:EFFECTIVE_DATE>
<wd:EFFECTIVE_SEQUENCE>1</wd:EFFECTIVE_SEQUENCE>
</wd:Caregiver_Events_group>
<wd:Caregiver_Events_group>
<wd:ASSIGNMENT_NUMBER>2168636-03012008E01</wd:ASSIGNMENT_NUMBER>
<wd:EFFECTIVE_DATE>2019-12-01-08:00</wd:EFFECTIVE_DATE>
<wd:EFFECTIVE_SEQUENCE>1</wd:EFFECTIVE_SEQUENCE>
</wd:Caregiver_Events_group>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_group>
<wd:UNIQUE_IDENTIFIER>2188946</wd:UNIQUE_IDENTIFIER>
</wd:Worker_group>
<wd:Caregiver_Events_group>
<wd:ASSIGNMENT_NUMBER>2188946-04272015E01</wd:ASSIGNMENT_NUMBER>
<wd:EFFECTIVE_DATE>2015-04-27-07:00</wd:EFFECTIVE_DATE>
<wd:EFFECTIVE_SEQUENCE>1</wd:EFFECTIVE_SEQUENCE>
</wd:Caregiver_Events_group>
<wd:Caregiver_Events_group>
<wd:COUNTRY_CODE>US</wd:COUNTRY_CODE>
<wd:ASSIGNMENT_NUMBER>2188946-04272015E01</wd:ASSIGNMENT_NUMBER>
<wd:EFFECTIVE_DATE>2019-12-01-08:00</wd:EFFECTIVE_DATE>
<wd:EFFECTIVE_SEQUENCE>1</wd:EFFECTIVE_SEQUENCE>
</wd:Caregiver_Events_group>
<wd:Caregiver_Events_group>
<wd:ASSIGNMENT_NUMBER>2188946-04272015E01</wd:ASSIGNMENT_NUMBER>
<wd:EFFECTIVE_DATE>2019-12-01-08:00</wd:EFFECTIVE_DATE>
<wd:EFFECTIVE_SEQUENCE>1</wd:EFFECTIVE_SEQUENCE>
</wd:Caregiver_Events_group>
</wd:Report_Entry>
</wd:Report_Data>
Portion of xslt:
<?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"
xmlns:wd="urn:com.workday.report/ERP-HCM-CR_ASSIGNMENTS_REPORT_V4" version="2.0">
<xsl:output method="text"/>
<xsl:variable name="linefeed" select="'
'"/>
<xsl:variable name="pipe" select="'|'"/>
<xsl:variable name="EffectiveStartDate" select="'01-01-1951'"/>
<xsl:variable name="EffectiveEndDate" select="'4712-12-31'"/>
<xsl:variable name="i" select="position()"/>
<xsl:param name="quote">"</xsl:param>
<xsl:template match="/">
<!-- File Header Record -->
<xsl:call-template name="Write-Header-Record0"/>
<!-- File Detail Layout -->
<xsl:for-each select="wd:Report_Data/wd:Report_Entry">
<xsl:for-each select="wd:Caregiver_Events_group">
<xsl:call-template name="Write-Detail-Record"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="Write-Header-Record0">
<xsl:text>Unique ID|Assignment Number|Effective Date|Sequence</xsl:text>
<xsl:value-of select="$linefeed"/>
</xsl:template>
<xsl:template name="Write-Detail-Record">
<xsl:value-of select="../wd:Worker_group/wd:UNIQUE_IDENTIFIER"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="wd:ASSIGNMENT_NUMBER"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="format-date(wd:EFFECTIVE_DATE, '[Y0001]-[M01]-[D01]')"/>
<xsl:value-of select="$pipe"/>
<xsl:choose>
<xsl:when
test="(preceding-sibling::node()[wd:EFFECTIVE_DATE = current()/wd:EFFECTIVE_DATE])">
<xsl:value-of
select="preceding-sibling::node()/wd:EFFECTIVE_SEQUENCE + current()/wd:EFFECTIVE_SEQUENCE"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="wd:EFFECTIVE_SEQUENCE"/>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$linefeed"/>
</xsl:template>
</xsl:stylesheet>
Can someone please assist. I know the value-of select cannot add two elements. If a worker (report_entry) has three events (caregiver_events_group) with same effective date, I would expect to see sequence of 1, 2, 3 on three rows.
Expecting output:
2168636|2168636-03012008E01|2008-03-01-08:00|1
2168636|2168636-03012008E01|2019-12-01-08:00|1
2168636|2168636-03012008E01|2019-12-01-08:00|2
2188946|2188946-04272015E01|2015-04-27-07:00|1
2188946|2188946-04272015E01|2019-12-01-08:00|1
2188946|2188946-04272015E01|2019-12-01-08:00|2

Would something like this work for you:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="urn:com.workday.report/ERP-HCM-CR_ASSIGNMENTS_REPORT_V4">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/Report_Data">
<!-- header -->
<xsl:text>Unique ID|Assignment Number|Effective Date|Sequence
</xsl:text>
<!-- data -->
<xsl:for-each select="Report_Entry">
<xsl:variable name="id" select="Worker_group/UNIQUE_IDENTIFIER" />
<xsl:for-each-group select="Caregiver_Events_group" group-by="EFFECTIVE_DATE">
<xsl:variable name="date" select="format-date(EFFECTIVE_DATE, '[Y0001]-[M01]-[D01]')"/>
<xsl:for-each select="current-group()">
<xsl:value-of select="$id"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="ASSIGNMENT_NUMBER"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="$date"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="position()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each-group>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Related

Multiple Counters in XSLT

I need to maintain 2 counters in my xslt - EntryID and RowID. I have a xml which contains Name and Certifications the person holds. Now I need to maintain one counter for each person (EntryID) and one for each certification (RowID). And on top of the certifications, I need to add one more certification "New" which will have a RowID one number higher than the maximum number certifications the person holds.
Below is the XML:
<?xml version='1.0' encoding='UTF-8'?>
<wd:Report_Data xmlns:wd="urn:com.workday.report/bsvc">
<wd:Report_Entry>
<Name>Ram</Name>
<Certifications>
<Certificate>AWS</Certificate>
<Certificate>Workday</Certificate>
<Certificate>SAP</Certificate>
</Certifications>
</wd:Report_Entry>
<wd:Report_Entry>
<Name>Nitin</Name>
<Certifications>
<Certificate>Workday</Certificate>
</Certifications>
</wd:Report_Entry>
<wd:Report_Entry>
<Name>Joe</Name>
<Certifications>
<Certificate>SAP</Certificate>
<Certificate>AWS</Certificate>
</Certifications>
</wd:Report_Entry>
</wd:Report_Data>
The expected output is below. The name should appear only in the first row.
EntryID,Name,RowID,Certification
1,Ram,1,AWS
1,,2,Workday
1,,3,SAP
1,,4,NEW --> New certificate with row id 4 as Ram already has 3 certifications
2,Nitin,1,Workday --> Entry ID is 2 for Nitin and Row ID restarts from 1
2,,2,NEW
3,Joe,1,SAP
3,,2,AWS
3,,3,NEW
The XSLT I am able to build so far, but not giving desired output.
<?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"
xmlns:wd="urn:com.workday.report/bsvc"
xmlns:etv="urn:com.workday/etv"
exclude-result-prefixes="xs wd" version="3.0">
<xsl:output method="text" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="delimeter" select="','"/>
<xsl:variable name="lineFeed" select="'
'"/>
<root>
<xsl:for-each select="wd:Report_Data/wd:Report_Entry">
<EntryID><xsl:value-of select="position()"/></EntryID>
<xsl:value-of select="$delimeter"/>
<Name><xsl:value-of select="Name"/></Name>
<xsl:value-of select="$delimeter"/>
<xsl:for-each select="Certifications/Certificate">
<RowID><xsl:value-of select="position()"/></RowID>
<xsl:value-of select="$delimeter"/>
<xsl:value-of select="."/>
</xsl:for-each>
<xsl:value-of select="$lineFeed"/>
<EntryID><xsl:value-of select="position()"/></EntryID>
<xsl:value-of select="$delimeter"/>
<xsl:value-of select="$delimeter"/>
<text>NEW</text>
<xsl:value-of select="$lineFeed"/>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Please help me with correct XSLT.
To produce the wanted output with XSLT 3, I would use something like
<?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"
exclude-result-prefixes="#all"
xmlns:wd="urn:com.workday.report/bsvc"
expand-text="yes">
<xsl:param name="delimeter" select="','"/>
<xsl:param name="lineFeed" select="'
'"/>
<xsl:output method="text"/>
<xsl:template match="wd:Report_Data">
<xsl:value-of select="'EntryID','Name','RowID','Certification'" separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
<xsl:apply-templates select="wd:Report_Entry"/>
</xsl:template>
<xsl:template match="wd:Report_Entry">
<xsl:apply-templates select="Certifications/Certificate"/>
</xsl:template>
<xsl:template match="Certificate">
<xsl:variable name="entry-id" as="xs:integer">
<xsl:number count="wd:Report_Entry"/>
</xsl:variable>
<xsl:variable name="row-id" as="xs:integer">
<xsl:number/>
</xsl:variable>
<xsl:value-of select="$entry-id, (../../Name[$row-id = 1], '')[1], $row-id, ." separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
<xsl:if test="position() = last()">
<xsl:value-of select="$entry-id, '', $row-id + 1, 'NEW'" separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The XML result you show can be produced quite easily using:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc"
exclude-result-prefixes="wd">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/wd:Report_Data">
<root>
<xsl:for-each select="wd:Report_Entry">
<row>
<EntryID>
<xsl:value-of select="position()" />
</EntryID>
<Name>
<xsl:value-of select="Name" />
</Name>
<xsl:for-each select="Certifications/Certificate">
<Certificate>
<RowID>
<xsl:value-of select="position()" />
</RowID>
<cName>
<xsl:value-of select="." />
</cName>
</Certificate>
</xsl:for-each>
<Certificate>
<RowID>
<xsl:value-of select="count(Certifications/Certificate) + 1" />
</RowID>
<cName>NEW</cName>
</Certificate>
</row>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
To get a "flat" CSV output, you can do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/wd:Report_Data">
<!-- header -->
<xsl:text>EntryID,Name,RowID,Certification
</xsl:text>
<!-- data -->
<xsl:for-each select="wd:Report_Entry">
<xsl:variable name="entry-data">
<xsl:value-of select="position()" />
<xsl:text>,</xsl:text>
<xsl:value-of select="Name" />
<xsl:text>,</xsl:text>
</xsl:variable>
<!-- output -->
<xsl:for-each select="Certifications/Certificate">
<xsl:copy-of select="$entry-data"/>
<xsl:value-of select="position()" />
<xsl:text>,</xsl:text>
<xsl:value-of select="." />
<xsl:text>
</xsl:text>
</xsl:for-each>
<!-- new certificate -->
<xsl:copy-of select="$entry-data"/>
<xsl:value-of select="count(Certifications/Certificate) + 1" />
<xsl:text>,NEW
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
which in XSLT 3.0 can be reduced to:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc"
expand-text="yes">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/wd:Report_Data">
<!-- header -->
<xsl:text>EntryID,Name,RowID,Certification
</xsl:text>
<!-- data -->
<xsl:for-each select="wd:Report_Entry">
<xsl:variable name="entry-data">{position()},{Name}</xsl:variable>
<!-- output -->
<xsl:for-each select="Certifications/Certificate, 'NEW'">{$entry-data},{position()},{.}
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT Help - Remove duplicate XML nodes based on two conditions

I need to remove duplicate nodes from xml using the following two keys:
Delete row/node IF [Assignment Number + Legal Entity + Effective Date] is duplicated.
Delete row/node IF [Assignment Number + Legal Entity + Payroll Definition + Time Card Required + Overtime Period] value is the same as previous row ONLY. Do not exclude if the value exists in the historical row but not previous row.
The xml is sorted by effective date. My xslt solves #2 using preceding-sibline node function and remove duplicates. Before it runs through this logic, It needs to first delete duplicate entries of <wd:Worker_BP_Events>for each <wd:Report_Entry> if the combination of Assignment Number+Legal Entity+Eff date are same.
xml:
<wd:Report_Data xmlns:wd="urn:com.workday.report/ERP-PAY-CR-PAYROLL_RELATIONSHIPS">
<wd:Report_Entry>
<wd:Worker_BP_Events>
<wd:COUNTRY_CODE>US</wd:COUNTRY_CODE>
<wd:UNIQUE_IDENTIFIER>2250403</wd:UNIQUE_IDENTIFIER>
<wd:ASSIGNMENT_NUMBER>2250403-06202016A14</wd:ASSIGNMENT_NUMBER>
<wd:LEGISLATIVE_DATA_GROUP>US Care LDG</wd:LEGISLATIVE_DATA_GROUP>
<wd:LEGAL_ENTITY>A14</wd:LEGAL_ENTITY>
<wd:EFFECTIVE_START_DATE>2018-12-23</wd:EFFECTIVE_START_DATE>
<wd:PAYROLL_DEFINITION wd:Descriptor="Biweekly A"> </wd:PAYROLL_DEFINITION>
<wd:TIMECARD_REQUIRED_FLAG>Y</wd:TIMECARD_REQUIRED_FLAG>
<wd:OVERTIME_PERIOD wd:Descriptor="TD 7 Days"> </wd:OVERTIME_PERIOD>
<wd:START_DATE>2018-12-23</wd:START_DATE>
</wd:Worker_BP_Events>
<wd:Worker_BP_Events>
<wd:COUNTRY_CODE>US</wd:COUNTRY_CODE>
<wd:UNIQUE_IDENTIFIER>2250403</wd:UNIQUE_IDENTIFIER>
<wd:ASSIGNMENT_NUMBER>2250403-06202016A14</wd:ASSIGNMENT_NUMBER>
<wd:LEGISLATIVE_DATA_GROUP>US Care LDG</wd:LEGISLATIVE_DATA_GROUP>
<wd:LEGAL_ENTITY>A14</wd:LEGAL_ENTITY>
<wd:EFFECTIVE_START_DATE>2019-06-23</wd:EFFECTIVE_START_DATE>
<wd:PAYROLL_DEFINITION wd:Descriptor="Biweekly A"> </wd:PAYROLL_DEFINITION>
<wd:TIMECARD_REQUIRED_FLAG>Y</wd:TIMECARD_REQUIRED_FLAG>
<wd:OVERTIME_PERIOD wd:Descriptor="TD 7 Days"> </wd:OVERTIME_PERIOD>
<wd:START_DATE>2019-06-23</wd:START_DATE>
</wd:Worker_BP_Events>
<wd:Worker_BP_Events>
<wd:COUNTRY_CODE>US</wd:COUNTRY_CODE>
<wd:UNIQUE_IDENTIFIER>2250403</wd:UNIQUE_IDENTIFIER>
<wd:ASSIGNMENT_NUMBER>2250403-06202016A14</wd:ASSIGNMENT_NUMBER>
<wd:LEGISLATIVE_DATA_GROUP>US Care LDG</wd:LEGISLATIVE_DATA_GROUP>
<wd:LEGAL_ENTITY>A14</wd:LEGAL_ENTITY>
<wd:EFFECTIVE_START_DATE>2019-06-23</wd:EFFECTIVE_START_DATE>
<wd:PAYROLL_DEFINITION wd:Descriptor="Biweekly A"> </wd:PAYROLL_DEFINITION>
<wd:TIMECARD_REQUIRED_FLAG>Y</wd:TIMECARD_REQUIRED_FLAG>
<wd:OVERTIME_PERIOD wd:Descriptor="TD 14 Days"> </wd:OVERTIME_PERIOD>
<wd:START_DATE>2019-06-23</wd:START_DATE>
</wd:Worker_BP_Events>
<wd:Worker_BP_Events>
<wd:COUNTRY_CODE>US</wd:COUNTRY_CODE>
<wd:UNIQUE_IDENTIFIER>2250403</wd:UNIQUE_IDENTIFIER>
<wd:ASSIGNMENT_NUMBER>2250403-06202016A09</wd:ASSIGNMENT_NUMBER>
<wd:LEGISLATIVE_DATA_GROUP>US Care LDG</wd:LEGISLATIVE_DATA_GROUP>
<wd:LEGAL_ENTITY>A09</wd:LEGAL_ENTITY>
<wd:EFFECTIVE_START_DATE>2020-03-15</wd:EFFECTIVE_START_DATE>
<wd:PAYROLL_DEFINITION wd:Descriptor="Biweekly A"> </wd:PAYROLL_DEFINITION>
<wd:TIMECARD_REQUIRED_FLAG>Y</wd:TIMECARD_REQUIRED_FLAG>
<wd:OVERTIME_PERIOD wd:Descriptor="TD 7 Days"> </wd:OVERTIME_PERIOD>
<wd:START_DATE>2020-03-15</wd:START_DATE>
</wd:Worker_BP_Events>
<wd:Worker>
<wd:EFFECTIVE_END_DATE>4712-12-31</wd:EFFECTIVE_END_DATE>
<wd:CLOSE_DATE>4712-12-31</wd:CLOSE_DATE>
</wd:Worker>
</wd:Report_Entry>
</wd:Report_Data>
xslt:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
xmlns:wd="urn:com.workday.report/ERP-PAY-CR-PAYROLL_RELATIONSHIPS"
version="2.0">
<xsl:output method="text"/>
<xsl:variable name="linefeed" select="'
'"/>
<xsl:variable name="pipe" select="'|'"/>
<xsl:param name="quote">"</xsl:param>
<xsl:template match="/">
<!-- File Detail Layout -->
<xsl:for-each select="wd:Report_Data/wd:Report_Entry/wd:Worker_BP_Events[not(concat(wd:ASSIGNMENT_NUMBER,wd:LEGAL_ENTITY,wd:PAYROLL_DEFINITION/#wd:Descriptor,wd:TIMECARD_REQUIRED_FLAG,wd:OVERTIME_PERIOD/#wd:Descriptor)=preceding-sibling::*[1]/concat(wd:ASSIGNMENT_NUMBER,wd:LEGAL_ENTITY,wd:PAYROLL_DEFINITION/#wd:Descriptor,wd:TIMECARD_REQUIRED_FLAG,wd:OVERTIME_PERIOD/#wd:Descriptor))]">
<xsl:call-template name="Write-Detail-Record"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="Write-Detail-Record">
<xsl:value-of select="wd:COUNTRY_CODE"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="wd:UNIQUE_IDENTIFIER"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="wd:ASSIGNMENT_NUMBER"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="wd:LEGISLATIVE_DATA_GROUP"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="wd:LEGAL_ENTITY"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="format-date(wd:EFFECTIVE_START_DATE,'[Y0001]-[M01]-[D01]')"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="format-date(../wd:Worker/wd:EFFECTIVE_END_DATE,'[Y0001]-[M01]-[D01]')"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="wd:PAYROLL_DEFINITION/#wd:Descriptor"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="wd:TIMECARD_REQUIRED_FLAG"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="wd:OVERTIME_PERIOD/#wd:Descriptor"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="format-date(wd:START_DATE,'[Y0001]-[M01]-[D01]')"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="format-date(../wd:Worker/wd:CLOSE_DATE,'[Y0001]-[M01]-[D01]')"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="format-date(wd:FINAL_CLOSE_DATE,'[Y0001]-[M01]-[D01]')"/>
<xsl:value-of select="$pipe"/>
<xsl:value-of select="format-date(../wd:Worker/wd:LAST_STANDARD_PROCESS_DATE,'[Y0001]-[M01]-[D01]')"/>
<xsl:value-of select="$linefeed"/>
</xsl:template>
</xsl:stylesheet>
Use for-each-group
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:wd="urn:com.workday.report/ERP-PAY-CR-PAYROLL_RELATIONSHIPS"
xpath-default-namespace="urn:com.workday.report/ERP-PAY-CR-PAYROLL_RELATIONSHIPS"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Report_Date">
<xsl:copy>
<xsl:variable name="unique-entries" as="element(Report_Entry)*">
<xsl:for-each-group select="Report_Entry" composite="yes" group-by="ASSIGNMENT_NUMBER, LEGAL_ENTITY, EFFECTIVE_START_DATE">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:variable>
<xsl:for-each-group select="$unique-entries" composite="yes" group-adjacent="ASSIGNMENT_NUMBER, LEGAL_ENTITY, PAYROLL_DEFINITION/#wd:Descriptor, TIMECARD_REQUIRED_FLAG, OVERTIME_PERIOD/#wd:Descriptor">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Is there a way to merge multiple XSLT’s to get output in a single output file?

I currently have a development that is generating three different files of three different status of employee from single XML input. Following is sample XML.
<wd:Report_Data xmlns:wd="urn:com.workday.report/Demo_Report">
<wd:Report_Entry>
<wd:Flag>HIRE</wd:Flag>
<wd:Userid>12345</wd:Userid>
<wd:FirstName>Jack</wd:FirstName>
<wd:LastName>Jones</wd:LastName>
<wd:BusinessTitle>Engineer</wd:businessTitle>
<wd:Country>US</wd:Country>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Flag>UPDATE</wd:Flag>
<wd:Userid>890767</wd:Userid>
<wd:FirstName>Mike</wd:FirstName>
<wd:LastName>Balder</wd:LastName>
<wd:BusinessTitle>Jr.Engineer</wd:businessTitle>
<wd:Country>US</wd:Country>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Flag>TERMINATE</wd:Flag>
<wd:Userid>543908</wd:Userid>
<wd:FirstName>Bolton</wd:FirstName>
<wd:LastName>James</wd:LastName>
<wd:BusinessTitle>Sr.Engineer</wd:businessTitle>
<wd:Country>US</wd:Country>
</wd:Report_Entry>
</wd:Report_Data>
I have following three XSLT’s that generates three files of different status of employee as shown below.
XSLT 1
<?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"
xmlns:xtt="urn:com.workday/xtt" xmlns:wd="urn:com.workday.report/Demo_Report"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
<xsl:output method="text"/>
<xsl:variable name="linefeed" select="'
'"/>
<xsl:template match="wd:Report_Data">
<File>
<xsl:text>user,add,abc#y.com,admin,ghy567</xsl:text>
<xsl:value-of select="$linefeed"/>
<xsl:text>"User id","FirstName","LastName","BusinessTitle","Country"</xsl:text>
<xsl:value-of select="$linefeed"/>
<!-- for each Employee section -->
<xsl:for-each select="/wd:Report_Data/wd:Report_Entry">
<xsl:if test="wd:Flag ='HIRE' and wd:Position Type !=''">
<xsl:text>"</xsl:text>
<xsl:value-of select="wd:Userid"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:FirstName"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:LastName"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:BusinessTitle"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:Country"/>
</xsl:if>
</xsl:for-each>
</File>
</xsl:template>
</xsl:stylesheet>
XSLT 2
<?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"
xmlns:xtt="urn:com.workday/xtt" xmlns:wd="urn:com.workday.report/Demo_Report"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
<xsl:output method="text"/>
<xsl:variable name="linefeed" select="'
'"/>
<xsl:template match="wd:Report_Data">
<File>
<xsl:text>user,update,abc#y.com,admin,ghy567</xsl:text>
<xsl:value-of select="$linefeed"/>
<xsl:text>"User id","FirstName","LastName","BusinessTitle","Country"</xsl:text>
<xsl:value-of select="$linefeed"/>
<!-- for each Employee section -->
<xsl:for-each select="/wd:Report_Data/wd:Report_Entry">
<xsl:if test="wd:Flag ='UPDATE' and wd:Position Type !=''">
<xsl:text>"</xsl:text>
<xsl:value-of select="wd:Userid"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:FirstName"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:LastName"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:BusinessTitle"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:Country"/>
</xsl:if>
</xsl:for-each>
</File>
</xsl:template>
</xsl:stylesheet>
XSLT 3
<?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"
xmlns:xtt="urn:com.workday/xtt" xmlns:wd="urn:com.workday.report/Demo_Report"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
<xsl:output method="text"/>
<xsl:variable name="linefeed" select="'
'"/>
<xsl:template match="wd:Report_Data">
<File>
<xsl:text>user,terminate,abc#y.com,admin,ghy567</xsl:text>
<xsl:value-of select="$linefeed"/>
<xsl:text>"User id","FirstName","LastName","BusinessTitle","Country"</xsl:text>
<xsl:value-of select="$linefeed"/>
<!-- for each Employee section -->
<xsl:for-each select="/wd:Report_Data/wd:Report_Entry">
<xsl:if test="wd:Flag ='TERMINATE' and wd:Position Type !=''">
<xsl:text>"</xsl:text>
<xsl:value-of select="wd:Userid"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:FirstName"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:LastName"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:BusinessTitle"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:Country"/>
</xsl:if>
</xsl:for-each>
</File>
</xsl:template>
</xsl:stylesheet>
Output File 1
user,add,abc#y.com,admin,ghy567
User id,FirstName,LastName,BusinessTitle,Country
12345,Jack,Jones,Engineer,US
Output File 2
user,update,abc#y.com,admin,ghy567
User id,FirstName,LastName,BusinessTitle,Country
890767,Mike,Balder,Jr.Engineer,US
Output File 3
user,terminate,abc#y.com,admin,ghy567
User id,FirstName,LastName,BusinessTitle,Country
543908,Bolton,James,Sr.Engineer,US
Now I have another requirement that 4th file must be generated with all the consolidated data from all the three files in a single file as following.
user,add,abc#y.com,admin,ghy567
User id,FirstName,LastName,BusinessTitle,Country
12345,Jack,Jones,Engineer,US
user,update,abc#y.com,admin,ghy567
User id,FirstName,LastName,BusinessTitle,Country
890767,Mike,Balder,Jr.Engineer,US
user,terminate,abc#y.com,admin,ghy567
User id,FirstName,LastName,BusinessTitle,Country
543908,Bolton,James,Sr.Engineer,US
So is there a way that we can merge three XSLT’s to get output in a single file ?
Using XSLT 3 or XQuery 3.1 you can run XSLT from XPath with fn:transform so in XQuery 3.1 you could use e.g.
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method 'text';
declare option output:item-separator '
';
declare variable $sheet-uris as xs:string* external := ('sheet1.xsl', 'sheet2.xsl', 'sheet3.xsl');
declare variable $sheets as document-node()* external := $sheet-uris ! doc(.);
let $xml := .
return
$sheets
!
transform(
map {
'stylesheet-node' : .,
'source-node' : $xml,
'delivery-format' : 'serialized'
}
)?output
or in XSLT 3
<?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"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:param name="sheet-uris" as="xs:string*" select="'sheet1.xsl', 'sheet2.xsl', 'sheet3.xsl'"/>
<xsl:param name="$sheets" as="document-node()*" select="$sheet-uris ! doc(.)"/>
<xsl:output method="text" item-separator="
"/>
<xsl:template match="/">
<xsl:sequence
select="
$sheets
!
transform(
map {
'stylesheet-node' : .,
'source-node' : current(),
'delivery-format' : 'serialized'
}
)?output"/>
</xsl:template>
</xsl:stylesheet>
And here is how you could do it in XProc 3 (currently supported by Morgana XProc III in beta, documented at https://www.xml-project.com/files/doc/manual.html):
<p:declare-step name="concat" xmlns:p="http://www.w3.org/ns/xproc" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-inline-prefixes="#all">
<p:input port="source"/>
<p:option name="sheet-uris" as="xs:string*" select="'sheet1.xsl', 'sheet2.xsl', 'sheet3.xsl'"/>
<p:output port="result" sequence="true" serialization="map { 'method' : 'text', 'item-separator' : '
' }"/>
<p:for-each>
<p:with-input select="$sheet-uris"/>
<p:xslt>
<p:with-input port="source" pipe="source#concat"/>
<p:with-input port="stylesheet" href="{.}"/>
</p:xslt>
</p:for-each>
</p:declare-step>
It uses the built-in p:for-each step of XProc https://spec.xproc.org/master/head/xproc/#p.for-each together with the p:xslt step https://spec.xproc.org/master/head/steps/#c.xslt to run the input source against the sequence of stylesheets provided as the sheet-uris option.
How about:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/Demo_Report">
<xsl:output method="text"/>
<xsl:variable name="header2" select="'User id,FirstName,LastName,BusinessTitle,Country
'"/>
<xsl:template match="/wd:Report_Data">
<!-- ADD -->
<xsl:text>user,add,abc#y.com,admin,ghy567
</xsl:text>
<xsl:value-of select="$header2"/>
<xsl:apply-templates select="wd:Report_Entry[wd:Flag='HIRE']"/>
<xsl:text>
</xsl:text>
<!-- UPDATE -->
<xsl:text>user,update,abc#y.com,admin,ghy567
</xsl:text>
<xsl:value-of select="$header2"/>
<xsl:apply-templates select="wd:Report_Entry[wd:Flag='UPDATE']"/>
<xsl:text>
</xsl:text>
<!-- TERMINATE -->
<xsl:text>user,terminate,abc#y.com,admin,ghy567
</xsl:text>
<xsl:value-of select="$header2"/>
<xsl:apply-templates select="wd:Report_Entry[wd:Flag='TERMINATE']"/>
</xsl:template>
<xsl:template match="wd:Report_Entry">
<xsl:text>"</xsl:text>
<xsl:value-of select="wd:Userid"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:FirstName"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:LastName"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:BusinessTitle"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="wd:Country"/>
<xsl:text>"
</xsl:text>
</xsl:template>
</xsl:stylesheet>

Add Trailer Sum Amount after for-each-group

I'm having a hard time trying to figure out how to add a trailer line after for-each-group. The trailer line should have the sum of each department below the for-each-group. Below is an example of what I mean.
Below is the xml:
<?xml version='1.0' encoding='UTF-8'?>
<wd:Report_Data xmlns:wd="urn:com.workday.report/bsvc">
<wd:Report_Entry>
<wd:Department>FISHING</wd:Department>
<wd:Ledger_Account>
<wd:Ledger_ID>99999999</wd:Ledger_ID>
</wd:Ledger_Account>
<wd:Transaction_Amount>1500</wd:Transaction_Amount>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Department>Clothing</wd:Department>
<wd:Ledger_Account>
<wd:Ledger_ID>44444444</wd:Ledger_ID>
</wd:Ledger_Account>
<wd:Transaction_Amount>250</wd:Transaction_Amount>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Department>Clothing</wd:Department>
<wd:Ledger_Account>
<wd:Ledger_ID>44444444</wd:Ledger_ID>
</wd:Ledger_Account>
<wd:Transaction_Amount>250</wd:Transaction_Amount>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Department>Clothing</wd:Department>
<wd:Ledger_Account>
<wd:Ledger_ID>22222222</wd:Ledger_ID>
</wd:Ledger_Account>
<wd:Transaction_Amount>500</wd:Transaction_Amount>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Department>Clothing</wd:Department>
<wd:Ledger_Account>
<wd:Ledger_ID>22222222</wd:Ledger_ID>
</wd:Ledger_Account>
<wd:Transaction_Amount>1500</wd:Transaction_Amount>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Department>FISHING</wd:Department>
<wd:Ledger_Account>
<wd:Ledger_ID>99999999</wd:Ledger_ID>
</wd:Ledger_Account>
<wd:Transaction_Amount>300</wd:Transaction_Amount>
</wd:Report_Entry>
</wd:Report_Data>
Below is my XSL:
<?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" version="2.0"
xmlns:wd="urn:com.workday.report/bsvc">
<xsl:strip-space elements="*"/>
<xsl:output indent="no" method="text"/>
<xsl:template match="/wd:Report_Data">
<detail>
<xsl:variable name="GroupDepartment">
<xsl:for-each-group select="wd:Report_Entry" group-by="wd:Department"></xsl:for-each-group>
</xsl:variable>
<xsl:variable name="GroupDepartmentAmount">
<xsl:value-of select="sum(wd:Report_Entry[$GroupDepartment]/wd:Transaction_Amount)"/>
</xsl:variable>
<xsl:for-each-group select="wd:Report_Entry" group-by="concat(wd:Department, wd:Ledger_Account/wd:Ledger_ID)">
<!-- Deptartment -->
<xsl:value-of select="wd:Department"/><xsl:call-template name="insertDelimiter"/>
<!-- Ledger Account -->
<xsl:value-of select="current-group()[1]/wd:Ledger_Account[1]/wd:Ledger_ID"/><xsl:call-template name="insertDelimiter"/>
<!-- Sum by Ledger Account and Department-->
<xsl:value-of select="sum(current-group()/wd:Transaction_Amount)"/><xsl:call-template name="insertDelimiter"/>
<xsl:call-template name="insertNewLine"/>
<!-- Now Sum but Department after each the last grouping of Department -->
<xsl:choose>
<xsl:when test="../$GroupDepartment[position() = last()]">
<!-- Dept ID -->
<xsl:value-of select="wd:Department"/><xsl:call-template name="insertDelimiter"/>
<!-- Amount -->
<xsl:value-of select="$GroupDepartmentAmount"/>
<xsl:call-template name="insertNewLine"/>
</xsl:when>
</xsl:choose>
</xsl:for-each-group>
</detail>
</xsl:template>
<xsl:template name="insertDelimiter">
<xsl:text>|</xsl:text>
</xsl:template>
<xsl:template name="insertNewLine">
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Below is my current output:
FISHING|99999999|1800|
FISHING|4300
Clothing|44444444|500|
Clothing|4300
Clothing|22222222|2000|
Clothing|4300
Below is desired output"
FISHING|99999999|1800|
FISHING|1800
Clothing|44444444|500|
Clothing|22222222|2000|
Clothing|2500
Please let me know if you have any questions. Any help is much appreciated!
-Remo
I would nest the two groupings:
<xsl:template match="Report_Data">
<xsl:for-each-group select="Report_Entry" group-by="Department">
<xsl:variable name="dep" select="current-grouping-key()"/>
<xsl:for-each-group select="current-group()" group-by="Ledger_Account/Ledger_ID">
<xsl:value-of select="$dep, current-grouping-key(), sum(current-group()/Transaction_Amount)" separator="|"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
<xsl:value-of select="$dep, sum(current-group()/Transaction_Amount)" separator="|"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
https://xsltfiddle.liberty-development.net/bdxtpJ/1 has the full code with
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="urn:com.workday.report/bsvc"
version="3.0">
<xsl:output method="text"/>
<xsl:template match="Report_Data">
<xsl:for-each-group select="Report_Entry" group-by="Department">
<xsl:variable name="dep" select="current-grouping-key()"/>
<xsl:for-each-group select="current-group()" group-by="Ledger_Account/Ledger_ID">
<xsl:value-of select="$dep, current-grouping-key(), sum(current-group()/Transaction_Amount)" separator="|"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
<xsl:value-of select="$dep, sum(current-group()/Transaction_Amount)" separator="|"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 Split comma seperated string into named nodes

A customer supplied XML contains the delivery address as a comma separated string, I would like to split this string into named nodes using XSLT 1.0.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<text>
Company, Streetaddress 20, 1234 AA, City
</text>
</root>
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<text>
<COMPANY>Company</COMPANY>
<ADDRESS>Streetaddress 20</ADDRESS>
<ZIPCODE>1234 AA</ZIPCODE>
<CITY>City</CITY>
</text>
</root>
I tried several recursive templates for XSLT 1.0 which do a fine job splitting but the resulting nodes are identically named.
If possible, how can this be achieved using XSLT 1.0?
Does it have to be a recursive template? How about a straight-forward chain of substring-before and substring-after like this:
<xsl:template match="text">
<xsl:copy>
<COMPANY>
<xsl:value-of select="normalize-space(substring-before(., ','))"/>
</COMPANY>
<xsl:variable name="s1" select="substring-after(., ',')"/>
<ADDRESS>
<xsl:value-of select="normalize-space(substring-before($s1, ','))"/>
</ADDRESS>
<xsl:variable name="s2" select="substring-after($s1, ',')"/>
<ZIPCODE>
<xsl:value-of select="normalize-space(substring-before($s2, ','))"/>
</ZIPCODE>
<CITY>
<xsl:value-of select="normalize-space(substring-after($s2, ','))"/>
</CITY>
</xsl:copy>
</xsl:template>
For the fun of it, here is a generic version using a recursive template.
<xsl:template match="text">
<xsl:copy>
<xsl:call-template name="parse-comma-separated">
<xsl:with-param name="elements" select="'COMPANY,ADDRESS,ZIPCODE,CITY'"/>
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="parse-comma-separated">
<xsl:param name="elements"/>
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($elements, ',')">
<xsl:element name="{normalize-space(substring-before($elements, ','))}">
<xsl:value-of select="normalize-space(substring-before($text, ','))"/>
</xsl:element>
<xsl:call-template name="parse-comma-separated">
<xsl:with-param name="elements" select="substring-after($elements, ',')"/>
<xsl:with-param name="text" select="substring-after($text, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{normalize-space($elements)}">
<xsl:value-of select="normalize-space($text)"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
As the supplied XML only had the address as single node (Streettaddress 20) I added a second variable to split the address string into streetaddress and housenumber.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="text">
<root>
<COMPANY>
<xsl:value-of select="normalize-space(substring-before(., ','))"/>
</COMPANY>
<xsl:variable name="s1" select="substring-after(., ',')"/>
<xsl:variable name="address_temp" select="normalize-space(substring-before($s1, ','))"/>
<ADDRESS>
<xsl:value-of select="normalize-space(substring-before($address_temp, ' '))"/>
</ADDRESS>
<HOUSENUMBER>
<xsl:value-of select="normalize-space(substring-after($address_temp, ' '))"/>
</HOUSENUMBER>
<xsl:variable name="s2" select="substring-after($s1, ',')"/>
<ZIPCODE>
<xsl:value-of select="normalize-space(substring-before($s2, ','))"/>
</ZIPCODE>
<CITY>
<xsl:value-of select="normalize-space(substring-after($s2, ','))"/>
</CITY>
</root>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<COMPANY>Company</COMPANY>
<ADDRESS>Streetaddress</ADDRESS>
<HOUSENUMBER>20</HOUSENUMBER>
<ZIPCODE>1234 AA</ZIPCODE>
<CITY>City</CITY>
</root>