XSLT Help - Remove duplicate XML nodes based on two conditions - xslt

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>

Related

Is it possible to provide a few values for the same param in XSL (vararg)?

I'm trying to create an XSL 2.0 template, which I would be able to call with a few <xsl:with-param/> values, all of them bound to the same <xsl:param>. In other words, I'm looking for a variadic template or its best alternative. Can you suggest some?
ps. This is what I came up with:
<xsl:template name="i">
<xsl:param name="args"/>
<xsl:for-each select="$args/*">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
Then, I'm calling it like this:
<xsl:template match="f">
<xsl:call-template name="i">
<xsl:with-param name="args"/>
<a><xsl:value-of select="./#one"/></a>
<a><xsl:value-of select="./#two"/></a>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
There are many ways to do this, below are just three of them:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pParams" select="3,5,15,22,7"/>
<xsl:param name="pParams2" select="'3,5,15,22,7'"/>
<xsl:param name="pParams3" select="/*/*[1], /*/*[2], /*/*[3] "/>
<xsl:template match="/*">
<xsl:for-each select="$pParams">
<xsl:sequence select=". * 2"/>
</xsl:for-each>
<xsl:value-of select="'
'"/>
<xsl:for-each select="tokenize($pParams2, ',')">
<xsl:sequence select="number(.) * 2"/>
</xsl:for-each>
<xsl:value-of select="'
'"/>
<xsl:for-each select="$pParams3">
<xsl:sequence select="name(.)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this XML document:
<t>
<a/>
<b/>
<c/>
</t>
the wanted result is produced:
6 10 30 44 14
6 10 30 44 14
a b c

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 - Compare preceding node and counter increment if the date is different

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>

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>

Create a recursive string function for adding a sequence

I've a challenging problem and so far I wasn't able to solve.
Within my xlst I have variable which contains a string.
I need to add the following sequence [eol] to this string.
On a fix position namely every 65 characters
I thought to use a function or template to recursive add this charackter.
The reason is that the string length can variate in length.
<xsl:function name="funct:insert-eol" as="xs:string" >
<xsl:param name="originalString" as="xs:string?"/>
<xsl:variable name="length">
<xsl:value-of select="string-length($originalString)"/>
</xsl:variable>
<xsl:variable name="start" as="xs:integer">
<xsl:value-of select="1"/>
</xsl:variable>
<xsl:variable name="eol" as="xs:integer">
<xsl:value-of select="65"/>
</xsl:variable>
<xsl:variable name="newLines">
<xsl:value-of select="$length idiv number('65')"/>
</xsl:variable>
<xsl:for-each select="1 to $newLines">
<xsl:value-of select="substring($originalString, $start, $eol)" />
</xsl:for-each>
</xsl:function>
The more I write code the more variables I need to introduce. This is still my lack on understanding.
For example we want every 5 chars an [eol]
aaaaaaabbbbbbccccccccc
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]cc
Hope someone has a starting point for me..
Regards Dirk
Rather straight-forward and short -- no recursion is necessary (and can even be specified as a single XPath expression):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pLLength" select="5"/>
<xsl:template match="/*">
<xsl:variable name="vText" select="string()"/>
<xsl:for-each select="1 to string-length($vText) idiv $pLLength +1">
<xsl:value-of select="substring($vText, $pLLength*(position()-1)+1, $pLLength)"/>
<xsl:if test=
"not(position() eq last()
or position() eq last() and string-length($vText) mod $pLLength)">[eol]</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this XML document:
<t>aaaaaaabbbbbbccccccccc</t>
the wanted, correct result is produced:
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]cc
When this XML document is processed:
<t>aaaaaaabbbbbbcccccccccddd</t>
again the wanted, correct result is produced:
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]ccddd[eol]
You can treat it as a grouping problem, using for-each-group:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:function name="mf:insert-eol" as="xs:string">
<xsl:param name="input" as="xs:string"/>
<xsl:param name="chunk-size" as="xs:integer"/>
<xsl:value-of>
<xsl:for-each-group select="string-to-codepoints($input)" group-by="(position() - 1) idiv $chunk-size">
<xsl:if test="position() gt 1"><xsl:sequence select="'eol'"/></xsl:if>
<xsl:sequence select="codepoints-to-string(current-group())"/>
</xsl:for-each-group>
</xsl:value-of>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<xsl:copy>
<xsl:sequence select="mf:insert-eol(., 5)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
That stylesheet transforms
<root>
<text>aaaaaaabbbbbbccccccccc</text>
</root>
into
<root>
<text>aaaaaeolaabbbeolbbbcceolccccceolcc</text>
</root>
Try this one:
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:param name="TextToChange" select="'aaaaaaabbbbbbccccccccc'"/>
<xsl:param name="RequiredLength" select="xs:integer(5)"/>
<xsl:template match="/">
<xsl:call-template name="AddText"/>
</xsl:template>
<xsl:template name="AddText">
<xsl:param name="Text" select="$TextToChange"/>
<xsl:param name="TextLength" select="string-length($TextToChange)"/>
<xsl:param name="start" select="xs:integer(1)"/>
<xsl:param name="end" select="$RequiredLength"/>
<xsl:choose>
<xsl:when test="$TextLength gt $RequiredLength">
<xsl:value-of select="substring($Text,$start,$end)"/>
<xsl:text>[eol]</xsl:text>
<xsl:call-template name="AddText">
<xsl:with-param name="Text" select="substring-after($Text, substring($Text,$start,$end))"/>
<xsl:with-param name="TextLength"
select="string-length(substring-after($Text, substring($Text,$start,$end)))"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>