How to remove duplicates including the first instance and just include the unique non-repetitive nodes? - xslt-2.0

I have the below sample XML code with me and the required output. I need to remove the duplicate rows, which is every instance. Can you help me how to achieve this using XSLT 2.0
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Row>
<Company>Xyzz</Company>
<Account>123567</Account>
<Amount>223.33</Amount>
</Row>
<Row>
<Company>Xyzz</Company>
<Account>123567</Account>
<Amount>223.33</Amount>
</Row>
<Row>
<Company>Xyzz</Company>
<Account>123567</Account>
<Amount>223.33</Amount>
</Row>
<Row>
<Company>pror</Company>
<Account>123567</Account>
<Amount>423.33</Amount>
</Row>
<Row>
<Company>abcd</Company>
<Account>123567</Account>
<Amount>123.33</Amount>
</Row>
</Root>
Required Output:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Row>
<Company>pror</Company>
<Account>123567</Account>
<Amount>423.33</Amount>
</Row>
<Row>
<Company>abcd</Company>
<Account>123567</Account>
<Amount>123.33</Amount>
</Row>
</Root>

Grouping in XSLT 2 and 3 is done using xsl:for-each-group (https://www.w3.org/TR/xslt-30/#element-for-each-group), so select="Row" as the grouping population, decide which grouping key you want, if you want to use all child element values as a composite grouping key you use xsl:for-each-group select="Row" composite="yes" group-by="*" in XSLT 3; inside the for-each-group you then have access to the current-group() and can only output (or in generally process) its first and only item if there is only one item in the group by selecting .[not(tail(current-group()))] as . represents the first item in the group and not(tail(current-group())) will be true if there is no second item in the group:
<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 indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Root">
<xsl:copy>
<xsl:for-each-group select="Row" composite="yes" group-by="*">
<xsl:apply-templates select=".[not(tail(current-group()))]"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ej9EGcj/

Related

Sum two elements by grouping using XSLT 2.0

I'm trying to sum two elements "amount" and "retroAmount" group by "tmid" using xslt 2.0 and I tried two methods, in method-1 everything is stacking up and in the method-2 it displays NaN. Any ideas about how this can be fixed?
Here is my XML file:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<row>
<tmid>abc</tmid>
<amount>651.03</amount>
<retroAmount>0</retroAmount>
</row>
<row>
<tmid>abc</tmid>
<amount>250.75</amount>
<retroAmount>-10</retroAmount>
</row>
<row>
<tmid>abc</tmid>
<amount>132</amount>
<retroAmount>-16.1</retroAmount>
</row>
<row>
<tmid>xyz</tmid>
<amount>129.19</amount>
<retroAmount>49.96</retroAmount>
</row>
<row>
<tmid>xyz</tmid>
<amount>148.76</amount>
<retroAmount>0</retroAmount>
</row>
<row>
<tmid>xyz</tmid>
<amount>92.29</amount>
<retroAmount>12</retroAmount>
</row>
</Request>
Output I am expecting:
<top xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Results>
<tmId>abc</tmId>
<total>1007.68</total>
</Results>
<Results>
<tmId>xyz</tmId>
<total>432.2</total>
</Results>
</top>
Any help is appreciated.
The XSLT code I was playing with:
Method-1 (everything is stacking up or being displayed without summing)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="Request/row"
group-by="tmid">
<row>
<tmid>
<xsl:value-of
select="current-grouping-key()"
/>
</tmid>
<xsl:for-each-group select="current-group()" group-by=".">
<amount>
<xsl:value-of select="sum(number(current-group()/amount))"/>
</amount>
<retroamount>
<xsl:value-of select="sum(number(current-group()/retroAmount))"/>
</retroamount>
</xsl:for-each-group>
</row>
</xsl:for-each-group>
</root>
</xsl:template>
</xsl:stylesheet>
Method-2 (I was only using "amount" and still it is displaying NaN, I would like to sum up both "amount" and "retroAmount"
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/*">
<top>
<xsl:for-each-group select="//tmid" group-by=".">
<Results>
<tmId>
<xsl:sequence
select="current-grouping-key()"
/>
</tmId>
<total>
<xsl:sequence select="sum(number(current-group()/amount))"/>
</total>
</Results>
</xsl:for-each-group>
</top>
</xsl:template>
</xsl:stylesheet>
You basically want
<xsl:template match="Request">
<xsl:copy>
<xsl:for-each-group select="row" group-by="tmid">
<Results>
<tmId>{current-grouping-key()}</tmId>
<total>{sum(current-group()!(amount, retroAmount))}</total>
</Results>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
(that is XSLT 3 with XPath 3.1 syntax, but in XSLT 2 with XPath 2 syntax you would use
<xsl:template match="Request">
<xsl:copy>
<xsl:for-each-group select="row" group-by="tmid">
<Results>
<tmId>
<xsl:value-of select="current-grouping-key()"/>
</tmId>
<total>
<xsl:value-of select="sum(current-group()/(amount, retroAmount))"/>
</total>
</Results>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
I only later noticed that the Request element is meant to be transformed to a top element so change the <xsl:template match="Request"><xsl:copy>...</xsl:copy></xsl:template> from above suggestions to <xsl:template match="Request"><step>...</step></xsl:template>.

Unable to group the element value using group by in XSLT

I have an XML lie below:
<Products>
<Product1>
<Reference>000510143244</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product1>
<Reference>45768799322</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
<Value1>543</Value1>
</Product2>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
<Value1>543</Value1>
</Product2>
</Products>
I want to get only first value of the Product1 reference...but I am unable to get that.Also it is not mandatory that Product 1 will always be the first element in input xml.
Any suggestions how can I get that?
I have tried to get the value as :
<xsl:template match="//Products">
<xsl:variable name="Product1">
<xsl:for-each-group select="/Reference" group-by="/Reference">
<xsl:copy-of select="." />
</xsl:for-each-group>
</xsl:variable>
</xsl:template>
Update:1
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
My expected output is :000510143244
To get the first occurrence of <Products> who has <Product1>, you might need to match the parent tag or root tag of your input XML.
Assuming your input as below:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<root>
<Products>
<Product2>
<Reference>35726318090</Reference>
</Product2>
</Products>
<Products>
<Product1>
<Reference>02563899183</Reference>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
</Product1>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
</Product2>
</Products>
</root>
The following code can give you the result:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="root">
<xsl:for-each-group select="Products/Product1" group-by="Reference">
<xsl:copy-of select="current-group()[1]" />
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
See the demo: https://xsltfiddle.liberty-development.net/3NJ38Zx
Update:
OR you can simply achieve it by following code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Update 2:
<xsl:template match="root">
<xsl:variable name="ref">
<xsl:for-each-group select="Products/Product1" group-by="Reference">
<xsl:copy-of select="current-group()[1]/Reference" />
</xsl:for-each-group>
</xsl:variable>
<xsl:value-of select="$ref"/>
</xsl:template>
https://xsltfiddle.liberty-development.net/3NJ38Zx/1
Update 3:
You cannot assign a value to global variable from a template.
There are two ways to get what you required.
1) Create a global variable as below which will take first <Products> whose child element is <Product1> and will display it's Reference
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:variable name="ref" select="root/Products[child::Product1][1]/Product1/Reference" />
<xsl:template match="/">
<xsl:value-of select="$ref" />
</xsl:template>
</xsl:stylesheet>
2) You can modify the template as below to get the result.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]/Product1/Reference">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>

Dynamically replacing substring in an given data(XML to fixed length)

Actually I have started with my XSLT work recently, I am facing difficulty in solving one of the requirement.
I am trying to fetch an substring from DATA element in the mentioned input i,e is ECHO and OKAY these codes need to be replaced with the values present under CODE/ECHO and CODE/OKAY in the same input. I had tried storing the substring in a variable and as the variable value and tag value would be same, I have tried to fetch that in . But its not working.
Is it that we cant use variables in the XPATHS or there is some other representation which needs to be used? Could anyone please help me with this.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:variable name="String" select="substring(DATA, (string-length(substring(DATA,0,77)) + 1), 4)" />
<xsl:variable name="String1" >
<xsl:value-of select="Root/CODES/$String" />
</xsl:variable>
<xsl:value-of select="$break" />
<xsl:value-of select="$String1" />
</xsl:for-each>
</xsl:template>
Input:
<?xml version='1.0' encoding='utf-8'?>
<ROOT>
<INPUT>
<I_FILENAME>ERES</I_FILENAME>
</INPUT>
<CODES>
<ECHO>A1</ECHO>
<OKAY>A2</OKAY>
</CODES>
<TABLES>
<T_ER>
<item>
<DATA> HEADERERESRGCITIS220190301124112000000RGERSD46</DATA>
</item>
<item>
<DATA>000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000ECHO00400300000000000XXXXXX 000{ P 2018-11-05</DATA>
</item>
<item>
<DATA>000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000OKAY00400300000000000XXXXXX 000{ P 2018-11-05</DATA>
</item>
<item>
<DATA>TRAILERERESRGCITIS220190301124112000000001570000</DATA>
</item>
</T_ER>
</TABLES>
</ROOT>
EXPECTED OUT PUT:
HEADERERESRGCITIS220190301124112000000RGERSD46
000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000A100400300000000000XXXXXX 000{ P 2018-11-05
000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000A200400300000000000XXXXXX 000{ P 2018-11-05
<xsl:template match="INPUT|CODES">
</xsl:template>
<xsl:template match="TABLES">
<xsl:variable name="break" select="'
'" />
<xsl:for-each select="T_ER/item"><xsl:value-of select="$break"></xsl:value-of>
<xsl:value-of select="DATA"/>
</xsl:for-each>
</xsl:template>

removing xml nodes using xslt but original XML namespace sand header should be retained

i checked the example in this link but it works for solution other way around , removees the nodes of the ID that i passed on to.
Removing XML Nodes using XSLT?
For example
<Message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dt="example.lessson.1:v3" >
<dt:Header>
<dt:MessageId>12121212121212121</dt:MessageId>
<dt:Timestamp>2013-01-01T00:00:00</dt:Timestamp>
<dt:MessageType>2</dt:MessageType>
</dt:Header>
<dt:Body >
<Rowsets>
<Rowset>
<Row>
<FirstName>Michael</FirstName>
<LastName>David</LastName>
<Phone>1234567890</Phone>
<ID>111111<ID>
</Row>
<Row>
<FirstName>David</FirstName>
<LastName>Michael</LastName>
<Phone>01234567890</Phone>
<ID>222222<ID>
</Row>
<Row>
<FirstName>Yang</FirstName>
<LastName>Christina</LastName>
<Phone>2345678901</Phone>
<ID>333333<ID>
</Row>
<Row>
<FirstName>Grey</FirstName>
<LastName>Meredith</LastName>
<Phone>3456789012</Phone>
<ID>4444444<ID>
</Row>
<Row>
<FirstName>David</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
<ID>5555555<ID>
</Row>
</Rowset>
</Rowsets>
</body>
I need to run an XSLt that will use the ID i pass and create a new xml with only that node and header and other body tags retained
example if i pass 111111,222222,333333 as the input the output should be
<Message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dt="example.lessson.1:v3" >
<dt:Header>
<dt:MessageId>12121212121212121</dt:MessageId>
<dt:Timestamp>2013-01-01T00:00:00</dt:Timestamp>
<dt:MessageType>2</dt:MessageType>
</dt:Header>
<dt:Body >
<Rowsets>
<Rowset>
<Row>
<FirstName>Michael</FirstName>
<LastName>David</LastName>
<Phone>1234567890</Phone>
<ID>111111<ID>
</Row>
<Row>
<FirstName>David</FirstName>
<LastName>Michael</LastName>
<Phone>01234567890</Phone>
<ID>222222<ID>
</Row>
<Row>
<FirstName>Yang</FirstName>
<LastName>Christina</LastName>
<Phone>2345678901</Phone<ID>333333<ID>
</Row>
</Rowset>
</Rowsets>
</body>
Use a parameter and compare the ID:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:param name="ids">111111,222222,333333</xsl:param>
<xsl:variable name="id-sequence" select="tokenize($ids, '\s*,\s*')"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row[not(ID = $id-sequence)]"/>
</xsl:transform>
Online at http://xsltransform.net/ejivdGF.

XSLT propagating Parent element into each child in a child list

Am new to XSLT and am just not able to wrap my head around this one.
I have an XML as follows:
<root>
<key>1</key>
<list>
<value>1</value>
<value>2</value>
</list>
</root>
What I want to achieve is this:
<root>
<row>
<key>1</key>
<value>1</value>
</row>
<row>
<key>1</key>
<value>2</value>
</row>
</root>
Your requirements are not entirely clear, but let's give it a try:
<xsl:template match="/root">
<xsl:copy>
<!-- each first following sibling after <key> -->
<xsl:apply-templates select="key/following-sibling::list[1]" />
</xsl:copy>
</xsl:template>
<xsl:template match="list">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="value">
<row>
<!-- the key immediately before the current <list> -->
<xsl:copy-of select="ancestor::list/preceding-sibling::key[1]" />
<xsl:copy-of select="." />
</row>
</xsl:template>
This code assumes a basic understanding of XSLT. And it assumes my understanding that a list element is preceded directly by a key element and that each row/value becomes a row with a value and the preceding key.
You tagged your question XSLT 2.0, but this code works with either XSLT 1.0 or 2.0 (and in this particular case will not be very different if you tried using the 2.0 features).
Tested with a 1.0 (.NET) and two 2.0 (Exselt and Saxon) processors against the following modified input:
<root>
<key>1</key>
<list>
<value>1</value>
<value>2</value>
</list>
<key>2</key>
<list>
<value>55</value>
<value>66</value>
<value>77</value>
</list>
</root>
Resulting in the following output:
<root>
<row>
<key>1</key>
<value>1</value>
</row>
<row>
<key>1</key>
<value>2</value>
</row>
<row>
<key>2</key>
<value>55</value>
</row>
<row>
<key>2</key>
<value>66</value>
</row>
<row>
<key>2</key>
<value>77</value>
</row>
</root>

Resources