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