Calculating counts when working with elements using name/value pairs - xslt

I have an xml in this format. I am looking to generate an output in the output format given below.
I am trying to calculate the total number of SkuID's associated with each unique SerialNum. The same SerialNum can be present more than once within the xml. Could you please suggest how I can get the count in this scenario?
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Skid>
<NameValuePairs>
<NameValue>
<ID>SerialNum</ID>
<Value>12345678</Value>
</NameValue>
<NameValue>
<ID>calcTotals</ID>
<Value>true</Value>
</NameValue>
</NameValuePairs>
<Box>
<SkuID>5729305323</SkuID>
</Box>
<Box>
<SkuID>2334923423</SkuID>
</Box>
</Skid>
<Skid>
<NameValuePairs>
<NameValue>
<ID>SerialNum</ID>
<Value>22334455</Value>
</NameValue>
<NameValue>
<ID>calcTotals</ID>
<Value>true</Value>
</NameValue>
</NameValuePairs>
<Box>
<SkuID>42342234234</SkuID>
</Box>
<Box>
<SkuID>3243325352</SkuID>
</Box>
</Skid>
<Skid>
<NameValuePairs>
<NameValue>
<ID>SerialNum</ID>
<Value>12345678</Value>
</NameValue>
<NameValue>
<ID>calcTotals</ID>
<Value>true</Value>
</NameValue>
</NameValuePairs>
<Box>
<SkuID>75547453333</SkuID>
</Box>
<Box>
<SkuID>235356233266</SkuID>
</Box>
</Skid>
</root>
Output:
<Skids>
<Box>
<TrackingNumber>12345678</TrackingNumber>
<Total>4</Total>
</Box>
<Box>
<TrackingNumber>22334455</TrackingNumber>
<Total>2</Total>
</Box>
</Skids>

Using XSLT 2.0 with for-each-group
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="root">
<Skids>
<xsl:for-each-group select="Skid" group-by="NameValuePairs/NameValue[ID='SerialNum']/Value">
<Box>
<TrackingNumber><xsl:value-of select=".//NameValue[ID='SerialNum']/Value"/></TrackingNumber>
<Total><xsl:value-of select="count(current-group()//Box)"/></Total>
</Box>
</xsl:for-each-group>
</Skids>
</xsl:template>
</xsl:stylesheet>
Using XSLT 1.0 with Muenchian Method
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="Tracking" match="Skid" use=".//NameValue[ID='SerialNum']/Value"/>
<xsl:template match="root">
<Skids>
<xsl:for-each select="Skid[generate-id() = generate-id(key('Tracking', .//NameValue[ID='SerialNum']/Value))]" >
<Box>
<TrackingNumber><xsl:value-of select=".//NameValue[ID='SerialNum']/Value"/></TrackingNumber>
<Total><xsl:value-of select="count(//Skid[.//NameValue[ID='SerialNum']/Value = current()//NameValue[ID='SerialNum']/Value]//Box)"/></Total>
</Box>
</xsl:for-each>
</Skids>
</xsl:template>
</xsl:stylesheet>
Output
<Skids>
<Box>
<TrackingNumber>12345678</TrackingNumber>
<Total>4</Total>
</Box>
<Box>
<TrackingNumber>22334455</TrackingNumber>
<Total>2</Total>
</Box>
</Skids>

The first part of my solution is a temporary variable, named ids,
and containing (no surprise) ID elements.
Each of them contains content of a Value element, which has ID sibling
with SerialNum content.
Additionally, this ID element has cnt attribute - the number of SkuID
elements in the "current" Skid.
Then ID elements in this variable are processed in a for-each-group loop,
grouping them by the content (ID value).
So the whole script looks like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform 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">
<Skids>
<xsl:variable name="ids">
<xsl:for-each select="//Value[../ID = 'SerialNum']">
<ID>
<xsl:attribute name="cnt" select="count(../../../Box/SkuID)"/>
<xsl:value-of select="."/>
</ID>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="$ids"/>
<xsl:for-each-group select="$ids/ID" group-by=".">
<Box>
<ID><xsl:value-of select="."/></ID>
<Total><xsl:value-of select="sum(current-group()/#cnt)"/></Total>
</Box>
</xsl:for-each-group>
</Skids>
</xsl:template>
</xsl:transform>
Note: <xsl:copy-of select="$ids"/> instruction was placed for
demonstration purpose only. In the final version remove it.

Related

XSLT Group by unique parameter

I am trying to convert the Input xml value to Output xml using xslt based on for-each-group logic of RAPID_ID
Input.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Response>
<Output>
<ID>1234</ID>
<CustomerName>KUMAR</CustomerName>
<BranchName>HARBOUR</BranchName>
<SchemeName>GOLD</SchemeName>
<MobileNumber>123456789</MobileNumber>
<CustomerType>PRIMARY</CustomerType>
<DedupeFound>NO</DedupeFound>
</Output>
<Output>
<ID>1234</ID>
<CustomerName>SEAN</CustomerName>
<BranchName>HARBOUR</BranchName>
<SchemeName>GOLD</SchemeName>
<MobileNumber>123456789</MobileNumber>
<CustomerType>SECONDARY</CustomerType>
<DedupeFound>YES</DedupeFound>
</Output>
<Output>
<ID>5678</ID>
<CustomerName>MARK</CustomerName>
<BranchName>CANTONMENT</BranchName>
<SchemeName>DIAMOND</SchemeName>
<MobileNumber>123456789</MobileNumber>
<CustomerType>PRIMARY</CustomerType>
<DedupeFound>NO</DedupeFound>
</Output>
<Output>
<ID>5678</ID>
<CustomerName>STEVE</CustomerName>
<BranchName>CANTONMENT</BranchName>
<SchemeName>DIAMOND</SchemeName>
<MobileNumber>123456789</MobileNumber>
<CustomerType>SECONDARY</CustomerType>
<DedupeFound>YES</DedupeFound>
</Output>
</Response>
My Expected output is
Output.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Response>
<Output>
<ID>1234</ID>
<CustomerName>KUMAR</CustomerName>
<BranchName>HARBOUR</BranchName>
<SchemeName>GOLD</SchemeName>
<MobileNumber>123456789</MobileNumber>
<DedupeDetails>
<CustomerType>PRIMARY</CustomerType>
<CustomerName>KUMAR</CustomerName>
<DedupeFound>NO</DedupeFound>
</DedupeDetails>
<DedupeDetails>
<CustomerType>SECONDARY</CustomerType>
<CustomerName>SEAN</CustomerName>
<DedupeFound>YES</DedupeFound>
</DedupeDetails>
</Output>
<Output>
<ID>5678</ID>
<CustomerName>MARK</CustomerName>
<BranchName>CANTONMENT</BranchName>
<SchemeName>DIAMOND</SchemeName>
<MobileNumber>123456789</MobileNumber>
<DedupeDetails>
<CustomerType>PRIMARY</CustomerType>
<CustomerName>MARK</CustomerName>
<DedupeFound>NO</DedupeFound>
</DedupeDetails>
<DedupeDetails>
<CustomerType>SECONDARY</CustomerType>
<CustomerName>STEVE</CustomerName>
<DedupeFound>YES</DedupeFound>
</DedupeDetails>
</Output>
</Response>
I started with something like this but not able to proceed further.
I am trying to group ID parameters first, Inside that It start with primary customer Details.
After Primary customer Details I have to iterate each customer ( Both Primary & secondary Here )
Any Suggestions / Corrections to make this achievable.
My XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Response>
<xsl:for-each-group select="/Response/Output" group-by="ID">
<xsl:sort select="ID"/>
</xsl:for-each-group>
</Response>
</xsl:template>
</xsl:stylesheet>
I would take a different approach:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="op" match="Output" use="ID"/>
<xsl:template match="/Response">
<xsl:copy>
<xsl:for-each select="Output[CustomerType='PRIMARY']">
<xsl:copy>
<xsl:copy-of select="ID|CustomerName|BranchName|SchemeName"/>
<xsl:for-each select="key('op', ID)">
<DedupeDetails>
<xsl:copy-of select="CustomerType|CustomerName|DedupeFound"/>
</DedupeDetails>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that this assumes that each group has exactly one PRIMARY customer and zero or more SECONDARY customers. If the input always has one PRIMARY customer followed by one SECONDARY customer, as in your example, then this could be even simpler.

Split large XML to smaller chunks by node count using XSLT

I have a requirement where we are getting a large XML file and I need to transform on small chunks
below is the XML sample with 4 records, I have to transform the XML so I am able to group them in chunks of 2.
<!-- Original XML-->
<EmpDetails>
<Records>
<EmpID>1</EmpID>
<Age>20</Age>
</Records>
<Records>
<EmpID>2</EmpID>
<Age>21</Age>
</Records>
<Records>
<EmpID>3</EmpID>
<Age>22</Age>
</Records>
<Records>
<EmpID>4</EmpID>
<Age>23</Age>
</Records>
</EmpDetails>
<!-- Expected XML-->
<EmpDetails>
<Split>
<Records>
<EmpID>1</EmpID>
<Age>20</Age>
</Records>
<Records>
<EmpID>2</EmpID>
<Age>21</Age>
</Records>
</Split>
<Split>
<Records>
<EmpID>3</EmpID>
<Age>22</Age>
</Records>
<Records>
<EmpID>4</EmpID>
<Age>23</Age>
</Records>
</Split>
</EmpDetails>
I tried few things including below without success.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<EmpDetails>
<xsl:for-each select="/EmpDetails/Records">
<Split>
<Records>
<EmpID>
<xsl:value-of select="EmpID"/>
</EmpID>
<Age>
<xsl:value-of select="Age"/>
</Age>
</Records>
</Split>
</xsl:for-each>
</EmpDetails>
</xsl:template>
</xsl:stylesheet>
Thanks
Yatan
group them in chunks of 2.
This could be done simply by:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/EmpDetails">
<xsl:copy>
<xsl:for-each select="Records[position() mod 2 = 1]">
<Split>
<xsl:copy-of select=". | following-sibling::Records[1]"/>
</Split>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Added:
To divide the records into groups of 200, you can do:
...
<xsl:for-each select="Records[position() mod 200 = 1]">
<Split>
<xsl:copy-of select=". | following-sibling::Records[position() < 200]"/>
</Split>
</xsl:for-each>
...
In XSLT 2.0 you could do:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/EmpDetails">
<xsl:copy>
<xsl:for-each-group select="Records" group-adjacent="(position() - 1) idiv 200">
<Split>
<xsl:copy-of select="current-group()"/>
</Split>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
use this code:
<xsl:for-each select="Records[position() mod 2 = 0]">
instead of this
<xsl:for-each select="Records[position() mod 2 = 1]">

Multiply 2 filtered numbers and sum

I need to sum the multiplication of 2 numbers based on this example
<test>
<stop>
<id>1</id>
<unit_id>1</unit_id>
<unit_id>2</unit_id>
</stop>
<stop>
<id>2</id>
<unit_id>1</unit_id>
<unit_id>3</unit_id>
</stop>
<unit>
<id>1</id>
<count>2</count>
<value>1</value>
</unit>
<unit>
<id>2</id>
<count>4</count>
<value>1</value>
</unit>
<unit>
<id>3</id>
<count>2</count>
<value>3</value>
</unit>
The result i want to get is the one below
<test>
<stop>
<id>1</id>
<sum>6</sum>
</stop>
<stop>
<id>2</id>
<sum>10</sum>
</stop>
Any tips how to get it?
I tried with this example but the sum of the moltiplication doesn't work, it is ok for only the sum or the multiplication but not both
<xsl:template match="stop">
<xsl:variable name="ship_unit" select="id"/>
<xsl:value-of select="sum(following-sibling::unit[id=$ship_unit]/count*following-sibling::unit[id=$ship_unit]/value)"/>
If I am guessing correctly, you want to do something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="unit" match="unit" use="id" />
<xsl:template match="/test">
<xsl:copy>
<xsl:for-each select="stop">
<xsl:variable name="unit1" select="key('unit', unit_id[1])" />
<xsl:variable name="unit2" select="key('unit', unit_id[2])" />
<xsl:copy>
<xsl:copy-of select="id"/>
<sum>
<xsl:value-of select="$unit1/count * $unit1/value + $unit2/count * $unit2/value" />
</sum>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However, the result of applying this to your input example will be:
<?xml version="1.0" encoding="utf-8"?>
<test>
<stop>
<id>1</id>
<sum>6</sum>
</stop>
<stop>
<id>2</id>
<sum>8</sum>
</stop>
</test>
and not what you posted.

creating xslt 1.0 to group element

Could you please help me with grouping in xslt .
Input.xml :
<EDI>
<Header/>
<Data>
<ISA> <Identifier>123</Identifier>
</ISA>
<Function>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>USD</Code></CUR>
</Transaction>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>EUR</Code></CUR>
</Transaction>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>USD</Code></CUR>
</Transaction>
</Function>
</Data>
</EDI>
I want to group the input xml based on CUR/Code . I have created key and generateid also but the in my output xml it reflecting ISA field value also before Output.
Output xml :
<Output>
<Filename>USAFile</Filename> (If the current-group has Code USD)
<EDI>
<Data><ISA><Identifier>12</Identifier></ISA>
<Function>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>USD</Code></CUR>
</Transaction>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>USD</Code></CUR>
</Transaction>
</Function>
</Data>
</EDI>
</Output>
<Output>
<Filename>EURFile</Filename> (If the current-group has Code EUR)
<EDI>
<Data><ISA><Identifier>23</Identifier></ISA>
<Function>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>EUR/Code></CUR>
</Transaction>
</Data>
</EDI>
</Output>
XSLT :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tib="http://www.tibco.com/bw/xslt/custom-functions" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:key name="groups" match="/EDI/Data/Function/Transaction" use="CUR/Code" />
<xsl:template match="Function">
<Function>
<xsl:for-each select="Transaction[generate-id() = generate-id(key('groups', CUR/Code)[1])]"/>
<xsl:for-each select="key('groups',CUR/Code)">
<Transaction>
<BR><Account><xsl:value-of select="BPR/Account"/></Account>
<CUR><Code><xsl:value-of select="CUR/Code"/></Code>
</Transaction>
</xsl:for-each>
</xsl:for-each>
</Function>
</xsl:template>
</xsl:stylesheet>
Firstly making your input XML well formed as below:
<?xml version="1.0" encoding="UTF-8"?>
<EDI>
<Header />
<Data>
<ISA>
<Identifier>123</Identifier>
</ISA>
<Function>
<Transaction>
<BPR>
<Account>
123
</Account>
</BPR>
<CUR>
<Code>USD</Code>
</CUR>
</Transaction>
<Transaction>
<BPR>
<Account>
123
</Account>
</BPR>
<CUR>
<Code>EUR</Code>
</CUR>
</Transaction>
<Transaction>
<BPR>
<Account>
123
</Account>
</BPR>
<CUR>
<Code>USD</Code>
</CUR>
</Transaction>
</Function>
</Data>
</EDI>
Using a bit different approach than what you tried (in case if it is okay with your case):
Note: In your output XML, you have shown two different values for <Identifier> in <ISA>. I guessed it as typo. Correct me if I am missing something.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="groups" match="/EDI/Data/Function/Transaction" use="CUR/Code" />
<xsl:template match="Function">
<xsl:apply-templates mode="group" />
</xsl:template>
<xsl:template match="Transaction[count(. | key('groups',CUR/Code)[1]) = 1]" mode="group">
<Output>
<Filename>
<xsl:choose>
<xsl:when test="CUR/Code = 'USD'">
<xsl:value-of select="'USAFile'" />
</xsl:when>
<xsl:when test="CUR/Code = 'EUR'">
<xsl:value-of select="'EURFile'" />
</xsl:when>
</xsl:choose>
</Filename>
<EDI>
<Data>
<ISA>
<Identifier>
<xsl:value-of select="../preceding-sibling::*[1]" />
</Identifier>
</ISA>
<Function>
<Transaction>
<xsl:copy-of select="key('groups',CUR/Code)" />
</Transaction>
</Function>
</Data>
</EDI>
</Output>
</xsl:template>
<xsl:template match="ISA" />
<!-- remove nodes other than through the key -->
<xsl:template match="node()" mode="group" />
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/94rmq63

XSLT transform to multiple complex types within for loop

I have a requirment to transform a XML with the below structure
<CustomerStatements>
<CustomerStatement>
<Name>ABC</Name>
<ID>1</ID>
<Amt>10</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>ABC</Name>
<ID>1</ID>
<Amt>20</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>XYZ</Name>
<ID>2</ID>
<Amt>30</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>XYZ</Name>
<ID>2</ID>
<Amt>40</Amt>
</CustomerStatement>
</CustomerStatements>
To
<Customers>
<Customer>
<Name>ABC</Name>
<Id>1</Id>
<Amounts>
<Amount>10</Amount>
<Amount>20</Amount>
</Amounts>
</Customer>
<Customer>
<Name>XYZ</Name>
<Id>2</Id>
<Amount>30</Amount>
<Amount>40</Amount>
</Customer>
</Customers>
I tried using a for loop and taking the name into a variable to compare the name in the next record, but this doesn't work. Can you any one help me with a sample XSLT psudo code.
Thanks
I. When this XSLT 1.0 solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key
name="kCustByNameId"
match="CustomerStatement"
use="concat(Name, '+', ID)" />
<xsl:template match="/*">
<Customers>
<xsl:apply-templates
select="CustomerStatement[
generate-id() =
generate-id(key('kCustByNameId', concat(Name, '+', ID))[1])]" />
</Customers>
</xsl:template>
<xsl:template match="CustomerStatement">
<Customer>
<xsl:copy-of select="Name|ID" />
<Amounts>
<xsl:for-each select="key('kCustByNameId', concat(Name, '+', ID))/Amt">
<Amount>
<xsl:apply-templates />
</Amount>
</xsl:for-each>
</Amounts>
</Customer>
</xsl:template>
</xsl:stylesheet>
...is applied to the OP's original XML:
<CustomerStatements>
<CustomerStatement>
<Name>ABC</Name>
<ID>1</ID>
<Amt>10</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>ABC</Name>
<ID>1</ID>
<Amt>20</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>XYZ</Name>
<ID>2</ID>
<Amt>30</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>XYZ</Name>
<ID>2</ID>
<Amt>40</Amt>
</CustomerStatement>
</CustomerStatements>
...the wanted result is produced:
<?xml version="1.0" encoding="UTF-8"?><Customers>
<Customer>
<Name>ABC</Name>
<ID>1</ID>
<Amounts>
<Amount>10</Amount>
<Amount>20</Amount>
</Amounts>
</Customer>
<Customer>
<Name>XYZ</Name>
<ID>2</ID>
<Amounts>
<Amount>30</Amount>
<Amount>40</Amount>
</Amounts>
</Customer>
</Customers>
The primary thing to look at here is Muenchian Grouping, which is the generally accepted method for grouping problems in XSLT 1.0.
II. Here's a more compact XSLT 2.0 solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<Customers>
<xsl:for-each-group select="CustomerStatement" group-by="ID">
<Customer>
<xsl:copy-of select="current-group()[1]/Name|current-group()[1]/ID" />
<Amounts>
<xsl:for-each select="current-group()/Amt">
<Amount>
<xsl:apply-templates />
</Amount>
</xsl:for-each>
</Amounts>
</Customer>
</xsl:for-each-group>
</Customers>
</xsl:template>
</xsl:stylesheet>
In this case, notice XSLT 2.0's use of the for-each-group element, which eliminates the need for the sometimes-verbose and potentially confusing Muenchian Grouping method.