Group by consecutive dates and sum by similar dates in xslt - xslt

Gurus
I am try to print data rows by consecutive dates and taking a sum of all the time off units in each row into by one summarized row with start date being the first day of the time off and end date being the last time off in the consecutive group. I used below xslt and it worked excellently however there could be case when a time off is cancelled in the system then for the same date I can get a negative value for the unit in which case my xslt below is not working as I am unable to handle similar dates and similar type scenarios. Any advise please.
XML
<?xml version='1.0' encoding='UTF-8'?>
<Data>
<Worker>
<Worker_ID>12</Worker_ID>
<Time_Off>
<Type>Compassionate Leave</Type>
<Date>2018-02-09-08:00</Date>
<Units>1</Units>
</Time_Off>
<Time_Off>
<Type>Compassionate Leave</Type>
<Date>2018-02-08-08:00</Date>
<Units>1</Units>
</Time_Off>
<Time_Off>
<Type>Compassionate Leave</Type>
<Date>2018-02-08-08:00</Date>
<Units>-1</Units>
</Time_Off>
<Time_Off>
<Type>Compassionate Leave</Type>
<Date>2018-02-01-08:00</Date>
<Units>1</Units>
</Time_Off>
<Time_Off>
<Type>Statutory Holiday</Type>
<Date>2018-02-07-08:00</Date>
<Units>1</Units>
</Time_Off>
<Time_Off>
<Type>Statutory Holiday</Type>
<Date>2018-02-06-08:00</Date>
<Units>1</Units>
</Time_Off>
</Worker>
</Data>
Xslt:
<xsl:stylesheet 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"
version="3.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:function name="mf:date" as="xs:date">
<xsl:param name="input" as="xs:string"/>
<xsl:sequence select="xs:date(substring($input, 1, 10))"/>
</xsl:function>
<xsl:function name="mf:line" as="xs:string">
<xsl:param name="group" as="element(Time_Off)*"/>
<xsl:value-of
select="$group[1]/../Worker_ID,
$group[1]/Type,
mf:date($group[1]/Date),
mf:date($group[last()]/Date),
sum($group/Units)"
separator=","/>
</xsl:function>
<xsl:template match="Worker">
<xsl:for-each-group select="Time_Off" group-by="Type">
<xsl:variable name="sorted-times" as="element(Time_Off)*">
<xsl:perform-sort select="current-group()">
<xsl:sort select="mf:date(Date)"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:for-each-group select="$sorted-times" group-by="mf:date(Date) - xs:dayTimeDuration('P1D') * position()">
<xsl:value-of select="mf:line(current-group()) || '
'"/>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
The output I get is below:
12,Compassionate Leave,2018-02-01,2018-02-01,1
12,Compassionate Leave,2018-02-08,2018-02-08,1
12,Compassionate Leave,2018-02-08,2018-02-09,0
12,Statutory Holiday,2018-02-06,2018-02-07,2
My desired output is below:
12,Compassionate Leave,2018-02-01,2018-02-01,1
12,Compassionate Leave,2018-02-08,2018-02-09,1
12,Statutory Holiday,2018-02-06,2018-02-07,2

You have posted an XSLT 3 stylesheet so I guess although you tagged the question as XSLT 2 it is fine to use XSLT 3, therefore here is a suggestion using xsl:iterate:
<xsl:stylesheet 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"
version="3.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:function name="mf:date" as="xs:date">
<xsl:param name="input" as="xs:string"/>
<xsl:sequence select="xs:date(substring($input, 1, 10))"/>
</xsl:function>
<xsl:function name="mf:line" as="xs:string">
<xsl:param name="group" as="element(Time_Off)*"/>
<xsl:value-of
select="$group[1]/../Worker_ID,
$group[1]/Type,
mf:date($group[1]/Date),
mf:date($group[last()]/Date),
sum($group/Units)"
separator=","/>
</xsl:function>
<xsl:template match="Worker">
<xsl:for-each-group select="Time_Off" group-by="Type">
<xsl:variable name="sorted-times" as="element(Time_Off)*">
<xsl:perform-sort select="current-group()">
<xsl:sort select="mf:date(Date)"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:iterate select="$sorted-times">
<xsl:param name="group" as="element(Time_Off)*" select="()"/>
<xsl:on-completion>
<xsl:value-of select="mf:line($group), ''" separator="
"/>
</xsl:on-completion>
<xsl:variable name="new-group" as="xs:boolean"
select="$group
and mf:date(Date) - mf:date($group[last()]/Date) gt xs:dayTimeDuration('P1D')"/>
<xsl:if test="$new-group">
<xsl:value-of select="mf:line($group), ''" separator="
"/>
</xsl:if>
<xsl:next-iteration>
<xsl:with-param name="group" select="if ($new-group) then . else ($group, .)"/>
</xsl:next-iteration>
</xsl:iterate>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Online at https://xsltfiddle.liberty-development.net/pPgCcov/2.

Related

How to get the grand total in an aggregate sum in XSLT?

First, I want to thank you for your time to help. I'm new to XSLT and am stuck for days trying to get the grand total of quantity. I am able to get the aggregate sum for each report_id by type and discard the negative aggregate sum (see report_id 222 and 444).
<?xml version="1.0" encoding="UTF-8"?>
<root>
<report>
<report_id>111</report_id>
<group>
<type>A</type>
<quantity>2</quantity>
</group>
<group>
<type>B</type>
<quantity>4</quantity>
</group>
<group>
<type>A</type>
<quantity>6</quantity>
</group>
</report>
<report>
<report_id>222</report_id>
<group>
<type>A</type>
<quantity>-5</quantity>
</group>
<group>
<type>A</type>
<quantity>2</quantity>
</group>
</report>
<report>
<report_id>333</report_id>
<group>
<type>B</type>
<quantity>7</quantity>
</group>
</report>
<report>
<report_id>444</report_id>
<group>
<type>B</type>
<quantity>-12</quantity>
</group>
</report>
</root>
My xsl output is below, and the problem lies at the last value 0.
111 A=8; 111 B=4; 333 B=7; 0
The output should be this
111 A=8; 111 B=4; 333 B=7; 19
I tried call-template to do the $grand_qty and failed. I prefer XSLT 2.0 but version is find too. Thanks in advance.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="xsl"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="text" />
<xsl:template match="/root">
<xsl:variable name="total_qty" select="0"/>
<xsl:variable name="grand_qty" select="0"/>
<xsl:for-each-group select="report/group" group-by="concat(../report_id, '|', type)">
<xsl:variable name="qty" select="sum(current-group()/quantity)"/>
<xsl:if test="$qty > 0">
<xsl:variable name="total_qty" select="$qty + $total_qty"/>
<xsl:value-of select="concat(../report_id, ' ', type, '=', $total_qty, '; ')"/>
<xsl:variable name="grand_qty" select="$grand_qty + $total_qty"/>
</xsl:if>
</xsl:for-each-group>
<xsl:value-of select="$grand_qty"/>
</xsl:template>
</xsl:stylesheet>
Variables in XSLT are immutable, and limited in scope to their parent element (or more precisely, to all following siblings and their descendants).
Try a different approach:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/root">
<xsl:variable name="groups">
<xsl:for-each-group select="report/group" group-by="concat(../report_id, ' ', type)">
<xsl:variable name="qty" select="sum(current-group()/quantity)"/>
<xsl:if test="$qty gt 0">
<group name="{current-grouping-key()}">
<xsl:value-of select="$qty"/>
</group>
</xsl:if>
</xsl:for-each-group>
</xsl:variable>
<xsl:value-of select="$groups/group/concat(#name, '=', ., '; ')" separator=""/>
<xsl:value-of select="sum($groups/group)"/>
</xsl:template>
</xsl:stylesheet>
As your code uses version="3.0" I would guess XSLT 3 also works; there you have some ways to store values with maps or arrays, xsl:iterate or fold-left; together with grouping it is possible but a bit convoluted to keep a total parameter as follows:
<?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:mf="http://example.com/mf"
expand-text="yes">
<xsl:function name="mf:group" as="map(*)*">
<xsl:param name="group-elements" as="element(group)*"/>
<xsl:for-each-group select="$group-elements" composite="yes" group-by="../report_id, type">
<xsl:variable name="qty" select="sum(current-group()/quantity)"/>
<xsl:sequence select="map { 'qty' : $qty, 'value' : current-grouping-key()[1] || ' ' || current-grouping-key()[2] || ' = ' || $qty }[$qty gt 0]"/>
</xsl:for-each-group>
</xsl:function>
<xsl:output method="text" item-separator="; "/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:iterate select="root/mf:group(report/group)">
<xsl:param name="total" select="0"/>
<xsl:on-completion>
<xsl:sequence select="$total"/>
</xsl:on-completion>
<xsl:sequence select="?value"/>
<xsl:next-iteration>
<xsl:with-param name="total" select="?qty + $total"/>
</xsl:next-iteration>
</xsl:iterate>
</xsl:template>
</xsl:stylesheet>
Alternative with fold-left instead of xsl:iterate:
<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:mf="http://example.com/mf"
expand-text="yes">
<xsl:function name="mf:group" as="map(*)*">
<xsl:param name="group-elements" as="element(group)*"/>
<xsl:for-each-group select="$group-elements" composite="yes" group-by="../report_id, type">
<xsl:variable name="qty" select="sum(current-group()/quantity)"/>
<xsl:sequence select="map { 'qty' : $qty, 'value' : current-grouping-key()[1] || ' ' || current-grouping-key()[2] || ' = ' || $qty }[$qty gt 0]"/>
</xsl:for-each-group>
</xsl:function>
<xsl:output method="text" item-separator="; "/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:sequence
select="fold-left(
root!mf:group(report!group),
0,
function($a, $g) { $a[position() lt last()], $g?value, $a[last()] + $g?qty }
)"/>
</xsl:template>
</xsl:stylesheet>

How to handle Nested 'LIST' with 'label' of each 'list-item'

In the below example we are trying to handle Nested 'LIST' with 'label' of each 'list-item':
Label Example:
style="ListNum1" then a.,b.,c., ... etc.
style="ListNum2" then (1),(2),(3), ... etc.
style="ListNum3" then (a),(b),(c), ... etc.
Can anyone help.
INPUT XML:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<list-item style="ListNum1"><p content-type="new">Your client may (<styled-content style="stat" style-type="Stat-Cal">Prob C §13659</styled-content>) because:</p></list-item>
<list-item style="ListNum2"><p content-type="new">For later income tax purposes</p></list-item>
<list-item style="ListNum3"><p content-type="new">Documents a “stepped-up basis” (<italic>i.e.,</italic> fair.</p></list-item>
<list-item style="ListNum3"><p content-type="new">Provides evidence for your client.</p></list-item>
<list-item style="ListNum2"><p content-type="new">If you or your <bold>client</bold>.</p></list-item>
<list-item style="ListNum1"><p content-type="new">If transfer <italic>unincorporated business</italic> appraisal. <styled-content style="stat" style-type="Stat-Cal">Prob C §13658</styled-content>.</p></list-item>
</body>
EXPECTED OUTPUT:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<list type="ListNum1">
<list-item style="ListNum1"><label>a.</label><p content-type="new">Your client may (<styled-content style="stat" style-type="Stat-Cal">Prob C §13659</styled-content>) because:</p></list-item>
<list type="ListNum2">
<list-item style="ListNum2"><label>(1)</label><p content-type="new">For later income tax purposes</p>
<list type="ListNum3">
<list-item style="ListNum3"><label>(a)</label><p content-type="new">Documents a “stepped-up basis” (<italic>i.e.,</italic> fair.</p></list-item>
<list-item style="ListNum3"><label>(b)</label><p content-type="new">Provides evidence for your client.</p></list-item>
</list></list-item>
<list-item style="ListNum2"><label>(2)</label><p content-type="new">If you or your <bold>client</bold>.</p></list-item>
</list></list-item>
<list-item style="ListNum1"><label>b.</label><p content-type="new">If transfer <italic>unincorporated business</italic> appraisal. <styled-content style="stat" style-type="Stat-Cal">Prob C §13658</styled-content>.</p></list-item>
</list>
</body>
XSLT CODE:
<?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"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="list-item[#style='ListNum1']">
<xsl:if test="not(preceding-sibling::*[1][self::list-item[#style='ListNum1']])">
<xsl:text disable-output-escaping="yes"><![CDATA[<list list-type="ListNum1">]]></xsl:text>
</xsl:if>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
<xsl:if test="not(following-sibling::*[1][self::list-item[#style='ListNum1']])">
<xsl:text disable-output-escaping="yes"><![CDATA[</list>]]></xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="list-item[#style='ListNum2']">
<xsl:if test="not(preceding-sibling::*[1][self::list-item[#style='ListNum2']])">
<xsl:text disable-output-escaping="yes"><![CDATA[<list list-type="ListNum2">]]></xsl:text>
</xsl:if>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
<xsl:if test="not(following-sibling::*[1][self::list-item[#style='ListNum2']])">
<xsl:text disable-output-escaping="yes"><![CDATA[</list>]]></xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="list-item[#style='ListNum3']">
<xsl:if test="not(preceding-sibling::*[1][self::list-item[#style='ListNum3']])">
<xsl:text disable-output-escaping="yes"><![CDATA[<list list-type="ListNum3">]]></xsl:text>
</xsl:if>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
<xsl:if test="not(following-sibling::*[1][self::list-item[#style='ListNum3']])">
<xsl:text disable-output-escaping="yes"><![CDATA[</list>]]></xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Reference URL # https://xsltfiddle.liberty-development.net/93nwMoZ/1
The recursive grouping is like this:
<?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:mf="http://example.com/mf"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="format-map" as="map(xs:integer, xs:string)"
select="map { 1 : 'a', 2 : '1', 3 : 'A' }"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="items" as="element(list-item)*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:where-populated>
<list type="ListNum{$level}">
<xsl:for-each-group select="$items" group-starting-with="list-item[#style = 'ListNum' || $level]">
<xsl:copy>
<label>{format-integer(position(), $format-map($level))}</label>
<xsl:apply-templates select="node(), mf:group(tail(current-group()), $level + 1)"/>
</xsl:copy>
</xsl:for-each-group>
</list>
</xsl:where-populated>
</xsl:function>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="body">
<xsl:copy>
<xsl:sequence select="mf:group(list-item, 1)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ei5R4v4
For the label formatting details, perhaps extend it as
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="format-map" as="map(xs:integer, xs:string)"
select="map { 1 : 'a', 2 : '1', 3 : 'a' }"/>
<xsl:function name="mf:format" as="xs:string">
<xsl:param name="number" as="xs:integer"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:variable name="formatted-number"
select="format-integer($number, $format-map($level))"/>
<xsl:sequence
select="if ($level = 1)
then $formatted-number || '.'
else '(' || $formatted-number || ')'"/>
</xsl:function>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="items" as="element(list-item)*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:where-populated>
<list type="ListNum{$level}">
<xsl:for-each-group select="$items" group-starting-with="list-item[#style = 'ListNum' || $level]">
<xsl:copy>
<label>{mf:format(position(), $level)}</label>
<xsl:apply-templates select="node(), mf:group(tail(current-group()), $level + 1)"/>
</xsl:copy>
</xsl:for-each-group>
</list>
</xsl:where-populated>
</xsl:function>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="body">
<xsl:copy>
<xsl:sequence select="mf:group(list-item, 1)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ei5R4v4/1

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>

XSLT - extract all text but last subsection

Looking to parse out a namespace from a full class name in xml.
Data example:
<results>
<test-case name="Co.Module.Class.X">
</results>
End result (going to csv format):
,Co.Module.Class
Stylesheet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="text" indent="yes" encoding="ISO-8859-1"/>
<xsl:param name="delim" select="','" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
FullTestName, Namespace
<xsl:apply-templates select="//test-case" />
</xsl:template>
<xsl:template match="test-case">
<xsl:apply-templates />
<xsl:value-of select="#name" />
<xsl:value-of select="$delim" />
<xsl:value-of select="function to go here for nameWithJustNamespace" />
<xsl:value-of select="$break" />
</xsl:template>
I understand the process would need a last index of "." to be called once, yet I'm not finding XSLT to have that function. How to best accomplish this?
To do this in pure XSLT 1.0, you need to call a named recursive template, e.g.:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/results">
<xsl:call-template name="remove-last-token">
<xsl:with-param name="text" select="test-case/#name"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="remove-last-token">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="'.'"/>
<xsl:value-of select="substring-before($text, $delimiter)"/>
<xsl:if test="contains(substring-after($text, $delimiter), $delimiter)">
<xsl:value-of select="$delimiter"/>
<xsl:call-template name="remove-last-token">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This pure XSLT 1.0 transformation (shorter, no conditional XSLT operations, single template):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="test-case[contains(#name, '.')]">
<xsl:param name="pDotIndex" select="0"/>
<xsl:variable name="vNextToken"
select="substring-before(substring(#name, $pDotIndex+1), '.')"/>
<xsl:value-of select="concat(substring('.', 2 - ($pDotIndex > 0)),$vNextToken)"/>
<xsl:variable name="vNewDotIndex" select="$pDotIndex+string-length($vNextToken)+1"/>
<xsl:apply-templates
select="self::node()[contains(substring(#name,$vNewDotIndex+1), '.')]">
<xsl:with-param name="pDotIndex" select="$vNewDotIndex"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<results>
<test-case name="Co.Module.Class.X"/>
</results>
produces the wanted, correct result:
Co.Module.Class
Part 2
With a slight modification the following transformation produces the complete CSV:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="test-case[contains(#name, '.')]">
<xsl:param name="pDotIndex" select="0"/>
<xsl:variable name="vNextToken"
select="substring-before(substring(#name, $pDotIndex+1), '.')"/>
<xsl:value-of select="concat(substring(',', 2 - (position() > 1)),
substring('.', 2 - ($pDotIndex > 0)), $vNextToken)"/>
<xsl:variable name="vNewDotIndex" select="$pDotIndex+string-length($vNextToken)+1"/>
<xsl:apply-templates
select="self::node()[contains(substring(#name,$vNewDotIndex+1), '.')]">
<xsl:with-param name="pDotIndex" select="$vNewDotIndex"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document:
<results>
<test-case name="Co.Module.Class.X"/>
<test-case name="Co2.Module2.Class2.Y"/>
<test-case name="Co3.Module3.Class3.Z"/>
</results>
the wanted, correct (CSV) result is produced:
Co.Module.Class,Co2.Module2.Class2,Co3.Module3.Class3

how to insert xml nodet at a defined points

xslt have function(like substring vice versa) or how to solve it? I had xml:
<document>
<Line>
<Line-Item>
<LineNumber>10</LineNumber>
<EAN>111</EAN>
<BIC>123123</BIC>
<SIC>AVD091</SIC>
</Line-Item>
</Line>
<Line>
<Line-Item>
<LineNumber>20</LineNumber>
<EAN>22222</EAN>
<BIC>3232332</BIC>
<SIC>AVD25482</SIC>
</Line-Item>
</Line>
</document>
needed output:
10 111 123123 AVD091
20 22222 3232332 AVD25482
Field line number start from 1 column position, EAN start from 11 column position, BIC start from 19 and SIC from 31.
Try this XSLT 1.0 style-sheet. The pad template is an XSLT 1.0 version of Martin's mf:pad function.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="document/Line/Line-Item"/>
</xsl:template>
<xsl:template name="pad">
<xsl:param name="value" />
<xsl:param name="width" />
<xsl:variable name="col-max" select="' '"/>
<xsl:value-of select="substring( concat($value,$col-max), 1, $width)" />
</xsl:template>
<xsl:template match="Line-Item" >
<xsl:call-template name="pad" >
<xsl:with-param name="value" select="LineNumber"/>
<xsl:with-param name="width" select="10" />
</xsl:call-template>
<xsl:call-template name="pad" >
<xsl:with-param name="value" select="EAN"/>
<xsl:with-param name="width" select="8" />
</xsl:call-template>
<xsl:call-template name="pad" >
<xsl:with-param name="value" select="BIC"/>
<xsl:with-param name="width" select="12" />
</xsl:call-template>
<xsl:value-of select="SIC" />
<xsl:value-of select="'
'" />
</xsl:template>
<xsl:template match="*" />
</xsl:stylesheet>
This short and generic transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<my:fields>
<fieldset name="LineNumber" width="10"/>
<fieldset name="EAN" width="8"/>
<fieldset name="BIC" width="12"/>
</my:fields>
<xsl:variable name="vSpaces" select="' '"/>
<xsl:variable name="vFields" select="document('')/*/my:fields/*"/>
<xsl:template match="Line-Item">
<xsl:text>
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="Line-Item/*">
<xsl:value-of select=
"concat(.,
substring($vSpaces,
1,
$vFields[#name = name(current())]/#width
-
string-length()
)
)"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<document>
<Line>
<Line-Item>
<LineNumber>10</LineNumber>
<EAN>111</EAN>
<BIC>123123</BIC>
<SIC>AVD091</SIC>
</Line-Item>
</Line>
<Line>
<Line-Item>
<LineNumber>20</LineNumber>
<EAN>22222</EAN>
<BIC>3232332</BIC>
<SIC>AVD25482</SIC>
</Line-Item>
</Line>
</document>
produces the wanted, correct result:
10 111 123123 AVD091
20 22222 3232332 AVD25482
Do note:
The element my:fields can be put in its own XML document. Thus, no modifications would be required to the XSLT code if some fields widths need to be modified.
Here is a sample stylesheet (XSLT 2.0, sorry, started writing before your comment indicated a request for 1.0):
<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:param name="col-max" as="xs:string" select="' '"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text"/>
<xsl:function name="mf:pad" as="xs:string">
<xsl:param name="input" as="xs:string"/>
<xsl:param name="col-length" as="xs:integer"/>
<xsl:sequence select="concat($input, substring($col-max, 1, $col-length - string-length($input)))"/>
</xsl:function>
<xsl:template match="Line">
<xsl:if test="position() > 1">
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="LineNumber">
<xsl:sequence select="mf:pad(., 10)"/>
</xsl:template>
<xsl:template match="EAN">
<xsl:sequence select="mf:pad(., 9)"/>
</xsl:template>
<xsl:template match="BIC">
<xsl:sequence select="mf:pad(., 12)"/>
</xsl:template>
<xsl:template match="SIC">
<xsl:sequence select="mf:pad(., string-length())"/>
</xsl:template>
</xsl:stylesheet>