XSLT Group and Merge - xslt

Trying to merge the following so that:
<?xml version="1.0" encoding="utf-8"?>
<Selections repeat="yes">
<Item>
<Title>One</Title>
<Details repeat="yes">
<item>
<Detail>First</Detail>
</item>
<item>
<Detail>Second</Detail>
</item>
</Details>
</Item>
<Item>
<Title>Two</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Three</Title>
<Details repeat="yes">
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Three</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Four</Title>
<Status></Status>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
</Selections>
becomes: (all Items/Details within any matching Title are merged)
<?xml version="1.0" encoding="utf-8"?>
<Selections repeat="yes">
<Item>
<Title>One</Title>
<Details repeat="yes">
<item>
<Detail>First</Detail>
</item>
<item>
<Detail>Second</Detail>
</item>
</Details>
</Item>
<Item>
<Title>Two</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Three</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Four</Title>
<Status></Status>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
</Selections>
Using XSLT 2.0 but not quite getting the group-by spot on as it appears to duplicate the entries.
Thanks in advance.
Sorry forget my attempt:
<xsl:template match="/">
<Selections repeat="yes">
<xsl:for-each-group select="/Selections/Item" group-by="Title">
<Item>
<Title>
<xsl:value-of select="current-grouping-key()"/>
</Title>
<Details repeat="yes">
<xsl:for-each select="current-group()">
<xsl:copy-of select="current-group() /Details/Item"/>
</xsl:for-each>
</Details>
</Item>
</xsl:for-each-group>
</Selections>
</xsl:template>
which gives:
<?xml version="1.0" encoding="UTF-8"?>
<Selections repeat="yes">
<Item>
<Title>One</Title>
<Details repeat="yes"/>
</Item>
<Item>
<Title>Two</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Three</Title>
<Details repeat="yes">
<Item>
<Detail>Third</Detail>
</Item>
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Four</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
</Selections>

The problem is with this line:
<xsl:for-each select="current-group()">
<xsl:copy-of select="current-group()/Details/Item"/>
</xsl:for-each>
With the xsl:for-each you are already iterating over the current-group(), so effectively for each item in the group, you then copy all items in the group.
To solve, just change it to this:
<xsl:for-each select="current-group()">
<xsl:copy-of select="Details/Item"/>
</xsl:for-each>
Alternatively, you don't need the xsl:for-each at all. The existing line does the job with the need for the xsl:for-each too.
<Details repeat="yes">
<xsl:copy-of select="current-group()/Details/Item"/>
</Details>

Related

XSLT selecting unique values using for-each

I want to extract unique values of element i tried for-each with following-sibling but it is not working
Main problem is that I don't know how to get distinct values using XSLT 1.0 or 2.0
Here is Source XML example i am using
<Items>
<Item>
<Itemno>112</Itemno>
<itemname>abc</itemname>
<qun>5</qun>
<loc>US</loc>
</Item>
</Items>
<Items>
<Item>
<Itemno>112</Itemno>
<itemname>abc</itemname>
<qun>6</qun>
<loc>UK</loc>
</Item>
</Items>
<Items>
<Item>
<Itemno>112</Itemno>
<itemname>abc</itemname>
<qun>11</qun>
<loc>GER</loc>
</Item>
</Items>
<Items>
<Item>
<Itemno>114</Itemno>
<itemname>lkj</itemname>
<qun>1</qun>
<loc>IND</loc>
</Item>
</Items>
Required Output
<Order>
<Item>
<Itemno>112</Itemno>
<itemname>abc</itemname>
<Desc>
<qun>5</qun>
<loc>US</loc>
</Desc>
<Desc>
<qun>6</qun>
<loc>UK</loc>
</Desc>
<Desc>
<qun>11</qun>
<loc>GER</loc>
</Desc>
</Item>
<Item>
<Itemno>114</Itemno>
<itemname>lkj</itemname>
<Desc>
<qun>1</qun>
<loc>IND</loc>
</Desc>
</Item>
</Order>
In xslt 2.0 you can for grouping by Itemno use for-each-group. As example you can use this code
<xsl:template match="Items">
<Order>
<xsl:for-each-group select="//Item" group-by="Itemno">
<Item>
<xsl:copy-of select="Itemno"/>
<xsl:copy-of select="itemname"/>
<xsl:for-each select="current-group()">
<Desc>
<xsl:copy-of select="qun"/>
<xsl:copy-of select="loc"/>
</Desc>
</xsl:for-each>
</Item>
</xsl:for-each-group>
</Order>
</xsl:template>
The result of it is xml that you need

XSLT, XPath, Using the value of current() to select and sum

I have a list of values that I need to summarize distinct item quantities. A simple version would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<Items xmlns:boomi="http://boomi.com/custom-function">
<Item>
<id>801</id>
<qty>0</qty>
</Item>
<Item>
<id>802</id>
<qty>1</qty>
</Item>
<Item>
<id>802</id>
<qty>1</qty>
</Item>
</Items>
I'm using the following XSLT
<Items>
<xsl:for-each select="distinct-values(/Items/Item/id)" >
<Item>
<id>
<xsl:value-of select="current()" />
</id>
</Item>
</xsl:for-each>
</Items>
To generate the following document
<?xml version="1.0" encoding="UTF-8"?>
<Items xmlns:boomi="http://boomi.com/custom-function">
<Item>
<id>801</id>
</Item>
<Item>
<id>802</id>
</Item>
</Items>
But I also need to include quantities. I've tried a few things, this being the closest, but doesn't work:
<Items>
<xsl:for-each select="distinct-values(/Items/Item/id)" >
<Item>
<id>
<xsl:value-of select="current()" />
<xsl:value-of select="sum(/Items/Item[id=current()]/qty)" />
</id>
</Item>
</xsl:for-each>
</Items>
I think it's not working because current() is an actual node? I'm trying to get to this:
<?xml version="1.0" encoding="UTF-8"?>
<Items xmlns:boomi="http://boomi.com/custom-function">
<Item>
<id>801</id>
<qty>0</qty>
</Item>
<Item>
<id>802</id>
<qty>2</qty>
</Item>
</Items>
Am I going about this in completely the wrong way? Thanks.
Here is the(a) solution using XSLT 2.0 indicated by michael.hor257k
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:boomi="http://boomi.com/custom-function">
<xsl:template match="/">
<Items>
<xsl:for-each-group select="/Items/Item" group-by="id" >
<Item>
<id>
<xsl:value-of select="current-grouping-key()" />
</id>
<qty>
<xsl:value-of select="sum(current-group()/qty)" />
</qty>
</Item>
</xsl:for-each-group>
</Items>
</xsl:template>
</xsl:stylesheet>

Perform an XSLT Logic

input:
<Move-Afile>
<Afile>
<Item>
<suppliercode>1</suppliercode>
<PackNumber>1234</PackNumber>
<Item85>
<Quantity>12</Quantity>
</Item85>
</Item>
<Item>
<suppliercode>2</suppliercode>
<PackNumber>567</PackNumber>
<Item85>
<Quantity>3</Quantity>
</Item85>
</Item>
<Item>
<suppliercode>1</suppliercode>
<PackNumber>567</PackNumber>
<Item85>
<Quantity>8</Quantity>
</Item85>
</Item>
<Item>
<suppliercode>3</suppliercode>
<PackNumber>126</PackNumber>
<Item85>
<Quantity>11</Quantity>
</Item85>
</Item>
<Item>
<suppliercode>4</suppliercode>
<PackNumber>876</PackNumber>
<Item85>
<Quantity>32</Quantity>
</Item85>
</Item>
</Afile>
</Move-Afile>
xslt:
i need the solution like the xsl should contain for-each structure like below.
If supplier codes are equal then we have to sum the quantity values of those nodes,otherwise directly map the quantity value.
<xsl:for-each select="/Move-Afile/Afile/Item">
<xsl:choose>
<xsl:when test="suppliercode=suppliercode>
<xsl:value-of select=sum(Quantity)"/><!-- sum of quantity where nodes have equal supplier code-->
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="Quantity"/><!-- map directly the quantity value-->
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
output:
<A>
<target>
<Item>
<Quantity>20</Quantity>
</Item>
<Item>
<Quantity>3</Quantity>
</Item>
<Item>
<Quantity>20</Quantity>
</Item>
<Item>
<Quantity>11</Quantity>
</Item>
<Item>
<Quantity>32</Quantity>
</Item>
</target>
</A>
You don't really need an xsl:choose here, just a single sum statement to sum all Quantity elements for Item elements with the same suppliercode (which include the current node you are on).
<xsl:value-of select="sum(//Item[suppliercode = current()/suppliercode]/Item85/Quantity)"/>
However, it would be more efficient to make use of a key to look for matching Item elements:
<xsl:key name="Item" match="Item" use="suppliercode" />
Then the sum statement is simplified to this
<xsl:value-of select="sum(key('Item', suppliercode)/Item85/Quantity)"/>
You might also like to consider using the XSLT identity transform in building your output XML, as this would be more flexible. You would probably only need a template matching the Quantity element in this case
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="Item" match="Item" use="suppliercode" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Quantity">
<xsl:copy>
<xsl:value-of select="sum(key('Item', ../../suppliercode)/Item85/Quantity)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<Move-Afile>
<Afile>
<Item>
<suppliercode>1</suppliercode>
<PackNumber>1234</PackNumber>
<Item85>
<Quantity>20</Quantity>
</Item85>
</Item>
<Item>
<suppliercode>2</suppliercode>
<PackNumber>567</PackNumber>
<Item85>
<Quantity>3</Quantity>
</Item85>
</Item>
<Item>
<suppliercode>1</suppliercode>
<PackNumber>567</PackNumber>
<Item85>
<Quantity>20</Quantity>
</Item85>
</Item>
<Item>
<suppliercode>3</suppliercode>
<PackNumber>126</PackNumber>
<Item85>
<Quantity>11</Quantity>
</Item85>
</Item>
<Item>
<suppliercode>4</suppliercode>
<PackNumber>876</PackNumber>
<Item85>
<Quantity>32</Quantity>
</Item85>
</Item>
</Afile>
</Move-Afile>
If you do want to change or remove other elements, just add separate templates for each case accordingly.

XSLT Grouping elements inside two siblings

I'm trying to wrap my head around this and I think the easiest way to explain it is to just show you, below. I've seen this but it doesn't always apply due to the fact I have standalone items also at the end which would match.
The seemingly tricky part is Whatever3 to Whatever6, and then Whatever7 and Whatever8, then finally the new position of Whatever9 - they need to be grouped and the original sequence maintained. (Ignore my naming, there is no way to use xsl:sort)
I've considered xsl:for-each with xsl:if inside, but issue is you can't guarantee how many "groups" vs "non-group" items there are.
Thanks!
XML
<root>
<item>
<position>1</position>
<label>Whatever1</label>
</item>
<item>
<position>2</position>
<label>Whatever2</label>
</item>
<item>
<position>3</position>
<label>Whatever3</label>
<marker id="unique1">start_group</marker>
</item>
<item>
<position>4</position>
<label>Whatever4</label>
</item>
<item>
<position>5</position>
<label>Whatever5</label>
</item>
<item>
<position>6</position>
<label>Whatever6</label>
<marker>last_in_group</marker>
</item>
<item>
<position>7</position>
<label>Whatever7</label>
<marker id="unique2">start_group</marker>
</item>
<item>
<position>8</position>
<label>Whatever8</label>
<marker>last_in_group</marker>
</item>
<item>
<position>9</position>
<label>Whatever9</label>
</item>
</root>
Result
<structure>
<item>
<position>1</position>
<label>Whatever1</label>
</item>
<item>
<position>2</position>
<label>Whatever2</label>
</item>
<group position="3" id="unique1">
<item>
<position>1</position>
<label>Whatever3</label>
</item>
<item>
<position>2</position>
<label>Whatever4</label>
</item>
<item>
<position>3</position>
<label>Whatever5</label>
</item>
<item>
<position>4</position>
<label>Whatever6</label>
</item>
</group>
<group position="4" id="uniqueid2">
<item>
<position>1</position>
<label>Whatever7</label>
</item>
<item>
<position>2</position>
<label>Whatever8</label>
</item>
</group>
<item>
<position>**5**</position>
<label>Whatever9</label>
</item>
</structure>
======================
Here is what I have so far, the only problem I have (besides it being messy) is Whatever4 and Whatever5 are showing up outside the Group.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="root">
<structure>
<xsl:apply-templates select="item[not(marker)] | item[marker='start_group']"/>
</structure>
</xsl:template>
<xsl:template match="item[marker='start_group']">
<group>
<xsl:variable name="currentPosition" select="number(position/text())"/>
<xsl:variable name="lastGroup" select="count(following-sibling::*[local-name() = 'item' and marker='last_in_group'][1]/preceding-sibling::*) + 1"/>
<xsl:attribute name="position">
<xsl:value-of select="$currentPosition"/>
</xsl:attribute>
<xsl:attribute name="id">
<xsl:value-of select="marker/#id"/>
</xsl:attribute>
<item>
<position><xsl:value-of select="number(position/text()) - $currentPosition + 1"/></position>
<label><xsl:value-of select="label/text()"/></label>
</item>
<!-- position() gets reset in for-loop, so need to adjust with outer position -->
<xsl:for-each select="following-sibling::item[(position() + $currentPosition) <= $lastGroup]">
<item>
<position><xsl:value-of select="number(position/text()) - $currentPosition + 1"/></position>
<label><xsl:value-of select="label/text()"/></label>
</item>
</xsl:for-each>
</group>
</xsl:template>
<xsl:template match="item[not(marker)]">
<item>
<position><xsl:value-of select="position/text()"/></position>
<label><xsl:value-of select="label/text()"/></label>
</item>
</xsl:template>
</xsl:stylesheet>
I. XSLT 1.0 Solution:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing"
match="item[not(marker[. = 'start_group'])
and
preceding-sibling::*[marker][1]/marker = 'start_group'
]"
use="generate-id(preceding-sibling::*
[marker[. = 'start_group']]
[1])"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[marker[. = 'start_group']]">
<group position="{1 +count(preceding-sibling::*[. = 'start_group'])}"
id="{marker/#id}">
<xsl:copy-of select=".|key('kFollowing', generate-id())"/>
</group>
</xsl:template>
<xsl:template match=
"item[not(marker[. = 'start_group'])
and
preceding-sibling::*[marker][1]/marker = 'start_group'
]"/>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<item>
<position>1</position>
<label>Whatever1</label>
</item>
<item>
<position>2</position>
<label>Whatever2</label>
</item>
<item>
<position>3</position>
<label>Whatever3</label>
<marker id="unique1">start_group</marker>
</item>
<item>
<position>4</position>
<label>Whatever4</label>
</item>
<item>
<position>5</position>
<label>Whatever5</label>
</item>
<item>
<position>6</position>
<label>Whatever6</label>
<marker>last_in_group</marker>
</item>
<item>
<position>7</position>
<label>Whatever7</label>
<marker id="unique2">start_group</marker>
</item>
<item>
<position>8</position>
<label>Whatever8</label>
<marker>last_in_group</marker>
</item>
<item>
<position>9</position>
<label>Whatever9</label>
</item>
</root>
produces the wanted, correct result:
<root>
<item>
<position>1</position>
<label>Whatever1</label>
</item>
<item>
<position>2</position>
<label>Whatever2</label>
</item>
<group position="1" id="unique1">
<item>
<position>3</position>
<label>Whatever3</label>
<marker id="unique1">start_group</marker>
</item>
<item>
<position>4</position>
<label>Whatever4</label>
</item>
<item>
<position>5</position>
<label>Whatever5</label>
</item>
<item>
<position>6</position>
<label>Whatever6</label>
<marker>last_in_group</marker>
</item>
</group>
<group position="1" id="unique2">
<item>
<position>7</position>
<label>Whatever7</label>
<marker id="unique2">start_group</marker>
</item>
<item>
<position>8</position>
<label>Whatever8</label>
<marker>last_in_group</marker>
</item>
</group>
<item>
<position>9</position>
<label>Whatever9</label>
</item>
</root>
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<root>
<xsl:for-each-group select="item" group-starting-with=
"*[marker eq 'start_group'
or
not(marker)
and
preceding-sibling::*[marker][1]/marker eq 'last_in_group'
]
">
<xsl:choose>
<xsl:when test="current-group()[1]/marker">
<group position=
"{1 +count(current-group()[1]
/preceding-sibling::*
[marker = 'start_group'])}"
id="{marker/#id}">
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</root>
</xsl:template>
</xsl:stylesheet>
When this XSLT 2.0 transformation is applied on the same XML document (above), the same correct result is produced.

Transform the Input xml into Output xml using xslt

Input XML structure:
<Customer>
<Order>
<item>
<name>ID</name>
<value>11111</value>
</item>
</Order>
<Order>
<item>
<name>ID</name>
<value>11111</value>
</item>
</Order>
<Order>
<item>
<name>ID</name>
<value>22222</value>
</item>
</Order>
<Order>
<item>
<name>ID</name>
<value>33333</value>
</item>
</Order>
</Customer>
Output should be :
<Customer>
<Order>
<item>
<name>ID</name>
<value>11111</value>
<item>
</Order>
<Order>
<item>
<name>ID</name>
<value>11111</value>
</item>
</Order>
</Customer>
<Customer>
<Order>
<item>
<name>ID</name>
<value>22222</value>
</item>
</Order>
</Customer>
<Customer>
<Order>
<item>
<name>ID</name>
<value>33333</value>
</item>
</Order>
</Customer>
Here the /Customer/<Order/item/value will come dynamically.
Please anyone give a solution for this transformation.
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kOrderByValue" match="Order" use="item/value"/>
<xsl:template match="Customer">
<xsl:for-each select="Order[count(.|key('kOrderByValue',
item/value
)[1]
) = 1]">
<Customer>
<xsl:apply-templates select="key('kOrderByValue',
item/value
)"/>
</Customer>
</xsl:for-each>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<Customer>
<Order>
<item>
<name>ID</name>
<value>11111</value>
</item>
</Order>
<Order>
<item>
<name>ID</name>
<value>11111</value>
</item>
</Order>
</Customer>
<Customer>
<Order>
<item>
<name>ID</name>
<value>22222</value>
</item>
</Order>
</Customer>
<Customer>
<Order>
<item>
<name>ID</name>
<value>33333</value>
</item>
</Order>
</Customer>
Note: grouping Customer's Order children by value.