Related
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>
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>
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>
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>
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>