xslt aggregation sum - xslt

I want do some sum of the values and return it as a row or column with the data.
taking the below xml as example:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<root>
<default0>
<Group>
<groupEntry>
<Day>Mon</Day>
<ID>111</ID>
<Number>-3</Number>
</groupEntry>
</Group>
<Group>
<groupEntry>
<Day>Tue</Day>
<ID>222</ID>
<Number>4</Number>
</groupEntry>
</Group>
<Group>
<groupEntry>
<Day>Tue</Day>
<ID>444</ID>
<Number>5</Number>
</groupEntry>
<Breakdown>
<Details>
<Day>Tue</Day>
<ID>444</ID>
<Number>-3</Number>
</Details>
<Details>
<Day>Tue</Day>
<ID>444</ID>
<Number>8</Number>
</Details>
</Breakdown>
</Group>
<Group>
<groupEntry>
<Day>Fri</Day>
<ID>333</ID>
<Number>-3</Number>
</groupEntry>
</Group>
</default0>
</root>
My below xslt :
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>ID,Day,Number</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="groupEntry|Details">
<xsl:text>
</xsl:text>
<xsl:value-of select="ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Number"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
returns this result:
ID,Day,Number
111,Mon,-3
222,Tue,4
444,Tue,5
444,Tue,-3
444,Tue,8
333,Fri,-3
However I want to get the total number by Day and report it in the result as either of the below 2 options
create 1 summary row like:
ID,Day,Number
Mon,Mon,-3
111,Mon,-3
Tue,Tue,9
222,Tue,4
444,Tue,5
444,Tue,-3
444,Tue,8
Fri,Fri,-3
333,Fri,-3
create an extra column:
ID,Day,Number,TotalNumber
111,Mon,-3,-3
222,Tue,4,9
444,Tue,5,9
444,Tue,-3,9
444,Tue,8,9
333,Fri,-3,-3
Does anyone know if this is possible?

In either option, you would probably need to define a key to group the elements by Day
<xsl:key name="days" match="groupEntry|Details" use="Day"/>
Then you can just add your extra column like so
<xsl:value-of select="sum(key('days', Day)/Number)"/>
Here is the full XSLT for the first option
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="days" match="groupEntry|Details" use="Day"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>ID,Day,Number,TotalNumber</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="groupEntry|Details">
<xsl:text>
</xsl:text>
<xsl:value-of select="ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Number"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="sum(key('days', Day)/Number)"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
This should output the following results
ID,Day,Number,TotalNumber
111,Mon,-3,-3
222,Tue,4,14
444,Tue,5,14
444,Tue,-3,14
444,Tue,8,14
333,Fri,-3,-3
In the second option, you would want to add a total line for the first occurrence of a particular Day. You can do this by checking if the current element is the first element in the key for that day
<xsl:if test="generate-id() = generate-id(key('days', Day)[1])">
Here is the XSLT for the second case
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="days" match="groupEntry|Details" use="Day"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>ID,Day,Number</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="groupEntry|Details">
<xsl:if test="generate-id() = generate-id(key('days', Day)[1])">
<xsl:text>
</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="sum(key('days', Day)/Number)"/>
</xsl:if>
<xsl:text>
</xsl:text>
<xsl:value-of select="ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Number"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
This should output the following results
ID,Day,Number
Mon,Mon,-3
111,Mon,-3
Tue,Tue,14
222,Tue,4
444,Tue,5
444,Tue,-3
444,Tue,8
Fri,Fri,-3
333,Fri,-3

Related

Multiple Counters in XSLT

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

XML element name has empty space, how to address it in XSLT?

I started learning Linux couple of days ago and currently I'm stuck at XSLT. Sorry if it's a stupid question or already answered elsewhere, I'm quite new here.
My XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<lac xmlns:t="http://smth.de">
<header>
<order id="20210323346730329408"/>
<adress id="IZ0009"/>
</header>
<items>
<item id="1"><material><code>IS-0001-BT-1</code><lotcode/></material><qty>10,000000</qty><expiry/></item>
<item id="2"><material><code>IS-0001-BT-2</code><lotcode/></material><qty>20,000000</qty><expiry/></item>
<item id="3"><material><code>IS-0001-AZ-1</code><lotcode/></material><qty>30,000000</qty><expiry/></item>
<item id="4"><material><code>IS-0001-AZ-2</code><lotcode/></material><qty>40,000000</qty><expiry/></item>
</items>
</lac>
I want to get this output:
Order ID,Adress ID,Item ID,MaterialCode,MaterialLotCode,MaterialQty,Expiry
20210323346730329408,IZ0009,1,IS-0001-BT-1,,10,000000,
20210323346730329408,IZ0009,2,IS-0001-BT-2,,20,000000,
20210323346730329408,IZ0009,3,IS-0001-AZ-1,,30,000000,
20210323346730329408,IZ0009,4,IS-0001-AZ-2,,40,000000,
This is what I got sofar, Your help is very welcome:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:text>Order ID,Adress ID,Item ID,MaterialCode,MaterialLotCode,MaterialQty,Expiry
</xsl:text>
<xsl:apply-templates mode="runHeader"/>
<xsl:apply-templates mode="runItems"/>
</xsl:template>
<xsl:template match="header" mode="runHeader">
<xsl:apply-templates mode="runOrder"/>
<xsl:apply-templates mode="runAdress"/>
</xsl:template>
<xsl:template match="order" mode="runOrder">
<xsl:value-of select="./#id"/>
<xsl:text>,</xsl:text>
</xsl:template>
<xsl:template match="adress" mode="runAdress">
<xsl:value-of select="./#id"/>
<xsl:text>,</xsl:text>
</xsl:template>
<xsl:template match="items" mode="runItems">
<xsl:for-each select="//item">
<xsl:value-of select="./#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="./material"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="./lotcode"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="./qty"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="./expiry"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Many thanks in advance.
I would suggest a different approach:
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="/lac">
<!-- header row -->
<xsl:text>Order ID,Adress ID,Item ID,MaterialCode,MaterialLotCode,MaterialQty,Expiry
</xsl:text>
<!-- common data -->
<xsl:variable name="common">
<xsl:value-of select="header/order/#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="header/adress/#id"/>
<xsl:text>,</xsl:text>
</xsl:variable>
<!-- data rows -->
<xsl:for-each select="items/item">
<xsl:copy-of select="$common"/>
<xsl:value-of select="#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="material/code"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="material/lotcode"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="qty"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="expiry"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note that this assumes that each item has at most one material (your XML structure allows for more).
--
P.S. That's not how you spell address.

XSLT 2.0 get distinct nodes names and preserve order

I am trying to transform XML to CSV where each entry does not contain all values. The column order must be preserved.
Initial file:
<?xml version='1.0' encoding='UTF-8'?>
<data>
<entry>
<a>FR</a>
<b>Dupont</b>
<c>123456</c>
<d>zzz</d>
<f>New York</f>
</entry>
<entry>
<a>FR</a>
<b>Martin</b>
<c>234561</c>
<d>xxx</d>
<e>2019-01-01</e>
<f>Paris</f>
</entry>
<entry>
<a>FR</a>
<b>Chris</b>
<c>345612</c>
<d>yyy</d>
<e>2019-01-01</e>
</entry>
</data>
Expected output:
a;b;c;d;e;f
FR;Dupont;123456;zzz;;New York
FR;Martin;234561;xxx;2019-01-01;Paris
FR;Chris;345612;yyy;2019-01-01;
I am struggling with getting the header values in the correct order. I tried distinct-values() and for-each-group() but I am not able to preserve the order.
One example of what I tried:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="distinct-values(//entry/*/local-name())">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
abcdfe
Any ideas? Thanks.
I would use the following approach:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="cols"
as="xs:string*"
select="let $max-cols := max(data/entry/count(*))
return distinct-values(data/entry[count(*) = $max-cols]/*/local-name())"/>
<xsl:template match="data">
<xsl:value-of select="$cols" separator=";"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="entry">
<xsl:value-of
select="for $col in $cols
return string(*[local-name() = $col])"
separator=";"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bFWR5Em/1
Try this (updated):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="#all">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="cols" as="element()*">
<xsl:for-each select="distinct-values(//entry/*/local-name())">
<xsl:sort select="."/>
<Item><xsl:value-of select="."/></Item>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$cols">
<xsl:value-of select="concat(.,';')"/>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:for-each select="//entry">
<xsl:variable name="entry" select="."/>
<xsl:for-each select="$cols">
<xsl:value-of select="concat($entry/*[local-name() = current()], ';')"/>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Grouping of grouped data

Input:
<persons>
<person name="John" role="Writer"/>
<person name="John" role="Poet"/>
<person name="Jacob" role="Writer"/>
<person name="Jacob" role="Poet"/>
<person name="Joe" role="Poet"/>
</persons>
Expected Output:
<groups>
<group roles="Wriet, Poet" persons="John, Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
As in the above example, I first need to group on person names and find everyone's roles. If more than one person is found to have the same set of roles (e.g. both John and Jacob are both Writer and Poet), then I need to group on each set of roles and list the person names.
I can do this for the first level of grouping using Muenchian method or EXSLT set:distinct etc.
<groups>
<group roles="Wriet, Poet" persons="John"/>
<group roles="Wriet, Poet" persons="Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
The above was transformed using XSLT 1.0 and EXSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sets="http://exslt.org/sets" extension-element-prefixes="sets">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="persons-by-name" match="person" use="#name"/>
<xsl:template match="persons">
<groups>
<xsl:for-each select="sets:distinct(person/#name)">
<group>
<xsl:attribute name="persons"><xsl:value-of select="."/></xsl:attribute>
<xsl:attribute name="roles">
<xsl:for-each select="key('persons-by-name', .)">
<xsl:value-of select="#role"/>
<xsl:if test="position()!=last()"><xsl:text>, </xsl:text></xsl:if>
</xsl:for-each>
</xsl:attribute>
</group>
</xsl:for-each>
</groups>
</xsl:template>
</xsl:stylesheet>
However, I need help to understand how to group on the grouped roles.
If XSLT 1.0 solution is not available, please feel free to recommend XSLT 2.0 approach.
Try it this way?
XSLT 1.0
(using EXSLT node-set() and distinct() functions)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="exsl set">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:key name="person-by-name" match="person" use="#name" />
<xsl:key name="person-by-roles" match="person" use="#roles" />
<xsl:variable name="distinct-persons">
<xsl:for-each select="set:distinct(/persons/person/#name)">
<person name="{.}">
<xsl:attribute name="roles">
<xsl:for-each select="key('person-by-name', .)/#role">
<xsl:sort/>
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</person>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<groups>
<xsl:for-each select="set:distinct(exsl:node-set($distinct-persons)/person/#roles)">
<group roles="{.}">
<xsl:attribute name="names">
<xsl:for-each select="key('person-by-roles', .)/#name">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</group>
</xsl:for-each>
</groups>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group roles="Poet, Writer" names="John, Jacob"/>
<group roles="Poet" names="Joe"/>
</groups>
I did exactly the same thing as you already did and then went a step further and grouped again. Now I get the following output with your input:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group roles="Writer,Poet" persons="John,Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
This is the XSLT 2.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns="" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:avintis="http://www.avintis.com/esb" exclude-result-prefixes="#all" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/persons">
<groups>
<xsl:variable name="persons" select="."/>
<!-- create a temporary variable containing all roles of a person -->
<xsl:variable name="roles">
<xsl:for-each select="distinct-values(person/#name)">
<xsl:sort select="."/>
<xsl:variable name="name" select="."/>
<xsl:element name="group">
<xsl:attribute name="roles">
<!-- sort the roles of each person -->
<xsl:variable name="rolesSorted">
<xsl:for-each select="$persons/person[#name=$name]">
<xsl:sort select="#role"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string-join($rolesSorted/person/#role,',')"/>
</xsl:attribute>
<xsl:attribute name="persons" select="."/>
</xsl:element>
</xsl:for-each>
</xsl:variable>
<!-- now loop again over all roles of the persons and group persons having the same roles -->
<xsl:for-each select="distinct-values($roles/group/#roles)">
<xsl:element name="group">
<xsl:variable name="name" select="."/>
<xsl:attribute name="roles" select="$name"/>
<xsl:attribute name="persons">
<xsl:value-of select="string-join($roles/group[#roles=$name]/#persons,',')"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</groups>
</xsl:template>
<xsl:template match="*|text()|#*">
<xsl:copy>
<xsl:apply-templates select="*|text()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The roles get also sorted - so independent from the input order of the roles and persons.

Concatenate data into one variable

An XML file has data like:
<AddtlStsRsnInf>/00000002/Level 2 Reject</AddtlStsRsnInf>
<AddtlStsRsnInf>The Transaction Reference Number is</AddtlStsRsnInf>
<AddtlStsRsnInf>not unique.</AddtlStsRsnInf>
How do you concatenate the data from all the three tags into a variable?
Thanks and regards,
Kiran
This may help:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="concat" match="/data">
<xsl:for-each select="AddtlStsRsnInf">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Given:
<?xml version="1.0"?>
<data>
<AddtlStsRsnInf>/00000002/Level 2 Reject</AddtlStsRsnInf>
<AddtlStsRsnInf>The Transaction Reference Number is</AddtlStsRsnInf>
<AddtlStsRsnInf>not unique.</AddtlStsRsnInf>
</data>
You can wrap it in a variable (v) using:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="concat" match="/data">
<xsl:variable name="v">
<xsl:for-each select="AddtlStsRsnInf">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$v" />
</xsl:template>
</xsl:stylesheet>
I think you would use something like:
<xsl:variable name="myVar" select="fn:string-join(//AddtlStsRsnInf/text(), ' ')" />
You'll need to adjust the XPath query if you're only supposed to select some AddtlStsRsnInf nodes.