Separate duplicates from unique - xslt

I tried with many XSLT methods like Key, for-each-group and match method options, but it is not working for me.
Input XML:
<?xml version='1.0' encoding='UTF-8'?>
<CustomerRecord>
<Customer>
<chargeto>ABC</chargeto>
<chargename>GARY</chargename>
</Customer>
<Customer>
<chargeto>XYZ</chargeto>
<chargename>DAVIS</chargename>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
</Customer>
</CustomerRecord>
Expected output XML:
<?xml version='1.0' encoding='UTF-8'?>
<Root>
<Customer_PO>
<Customer>
<chargeto>ABC</chargeto>
<chargename>GARY</chargename>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
</Customer>
</Customer_PO>
<Customer_Falty>
<Customer>
<chargeto>XYZ</chargeto>
<chargename>DAVIS</chargename>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
</Customer>
</Customer_Falty>
</Root>
Below is the XSLT which I wrote Initially to at least get the some details in the Output, but data is getting missed when the target is generated:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var" version="1.0" xmlns:ns0="http://Namespace">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key use="chargeto" match="/CustomerRecord/Customer" name ="groups"/>
<xsl:template match="/">
<xsl:apply-templates select="/CustomerRecord" />
</xsl:template>
<xsl:template match="/CustomerRecord">
<Root>
<xsl:for-each select="/CustomerRecord/Customer[generate-id(.)=generate-id(key('groups',chargeto))]">
<Customer_PO>
<xsl:for-each select="key('groups',chargeto)">
<Customer>
<chargeto>
<xsl:value-of select="CustomerRecord/chargeto/text()" />
</chargeto>
<chargename>
<xsl:value-of select="CustomerRecord/chargename/text()" />
</chargename>
</Customer>
</xsl:for-each>
</Customer_PO>
</xsl:for-each>
</Root>
</xsl:template>
</xsl:stylesheet>
Please find the explanation about the Expected Output
<Customer_PO>. Under this tag we will have all the <Customer> segments where all the <chargeto> fields are having duplicate value
<Customer_Falty> Under this tag we will have all the <Customer> segment where all the <chargeto> field values are unique

If I understand correctly your explanation, you want to do something like:
XSL 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="cust-by-charge" match="Customer" use="chargeto" />
<xsl:template match="/CustomerRecord">
<Root>
<Customer_PO>
<xsl:copy-of select="Customer[count(key('cust-by-charge', chargeto)) > 1]"/>
</Customer_PO>
<Customer_Falty>
<xsl:copy-of select="Customer[count(key('cust-by-charge', chargeto)) = 1]"/>
</Customer_Falty>
</Root>
</xsl:template>
</xsl:stylesheet>
Possibly there's a more elegant approach that would count the size of each group only once.

Related

Filtering Common Values which is not equals to blank using XSLT

Need help on below case to filter Output based on the common Value and store all the common value apart from Blank space in one tag and remaining in other tag.
INPUT PAYLOAD:
===============
<?xml version='1.0' encoding='UTF-8'?>
<CustomerRecord>
<Customer>
<chargeto>ABC</chargeto>
<chargename>GARY</chargename>
<customer_number>EP10025</customer_number>
</Customer>
<Customer>
<chargeto>XYZ</chargeto>
<chargename>DAV</chargename>
<customer_number></customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number></customer_number>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10024</customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number>EP10025</customer_number>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10022</customer_number>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>XYZ</chargename>
<customer_number>EP10023</customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number>EP10025</customer_number>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10023</customer_number>
</Customer>
</CustomerRecord>
Expected OutPut :
====================
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Record>
<Customer>
<chargeto>ABC</chargeto>
<chargename>GARY</chargename>
<customer_number>EP10025</customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number>EP10025</customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number>EP10025</customer_number>
</Customer>
</Record>
<Record>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10023</customer_number>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>XYZ</chargename>
<customer_number>EP10023</customer_number>
</Customer>
</Record>
<Record>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10024</customer_number>
</Customer>
</Record>
<Record>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10022</customer_number>
</Customer>
</Record>
<Record>
<Customer>
<chargeto>XYZ</chargeto>
<chargename>DAV</chargename>
<customer_number/>
</Customer>
</Record>
<Record>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number/>
</Customer>
</Record>
</Root>
XSLT Transformation added:
============================
<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="/CustomerRecord">
<xsl:variable name="groups">
<xsl:for-each-group select="Customer" group-by="customer_number">
<group size="{count(current-group())}">
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</xsl:variable>
<Root>
<xsl:for-each select="$groups/group[#size > 1]">
<Record>
<xsl:copy-of select="Customer"/>
</Record>
</xsl:for-each>
<Record>
<xsl:copy-of select="$groups/group[#size = 1]/Customer"/>
</Record>
</Root>
</xsl:template>
</xsl:stylesheet>
Need all the common <customer_number> field values in one apart from blank values in <Record> and remaining in other <Record> tags.
-- edited in response to clarification of requirements --
As noted in comments below, this is a rather trivial exercise and I don't see why you could not do this on your own:
XSLT 2.0
<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="/CustomerRecord">
<Root>
<xsl:for-each-group select="Customer" group-by="customer_number">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<Record>
<xsl:copy-of select="current-group()"/>
</Record>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="current-group()">
<Record>
<xsl:copy-of select="."/>
</Record>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</Root>
</xsl:template>
</xsl:stylesheet>

Filter Common Field Value in One each <Record> Target Field Tag and others in individual <Record> Field Tags using XSLT

Need Help on the below XSLT scenario
```
Input Payload:
===============
<?xml version='1.0' encoding='UTF-8'?>
<CustomerRecord>
<Customer>
<chargeto>ABC</chargeto>
<chargename>GARY</chargename>
<customer_number>EP10025</customer_number>
</Customer>
<Customer>
<chargeto>XYZ</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10023</customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number>EP10023</customer_number>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10024</customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number>EP10025</customer_number>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10022</customer_number>
</Customer>
</CustomerRecord>```
Below is the Expected Output
```Expected Output Payload:
===========================
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Customer_PO>
<Customer>
<chargeto>ABC</chargeto>
<chargename>GARY</chargename>
<customer_number>EP10025</customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number>EP10025</customer_number>
</Customer>
</Customer_PO>
<Customer_PO>
<Customer>
<chargeto>XYZ</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10023</customer_number>
</Customer>
<Customer>
<chargeto>CDE</chargeto>
<chargename>GARY DAVIS</chargename>
<customer_number>EP10023</customer_number>
</Customer>
</Customer_PO>
<Customer_Faulty>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10024</customer_number>
</Customer>
<Customer>
<chargeto>ABC</chargeto>
<chargename>DAV</chargename>
<customer_number>EP10022</customer_number>
</Customer>
</Customer_Faulty>
</Root>```
I have below XSLT which is partially working fine
``` XSLT Code:
===============
<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="cust-by-charge" match="Customer" use="customer_number" />
<xsl:template match="/CustomerRecord">
<Root>
<Customer_PO>
<xsl:copy-of select="Customer[count(key('cust-by-charge', customer_number)) > 1]"/>
</Customer_PO>
<Customer_Faulty>
<xsl:copy-of select="Customer[count(key('cust-by-charge', customer_number)) = 1]"/>
</Customer_Faulty>
</Root>
</xsl:template>
</xsl:stylesheet>
```
**I am partially getting output correctly, I need to create multiple <Customer_PO> segments for the common <customer_number> field values.
In our current case all common values are getting added into same <Customer_PO>. --> This part is not working fine
And Non Common <customer_number> field values will be set under <Customer_Faulty> -- This part is working Fine**
I am again struggling to understand your explanation. I think this does what you want:
XSLT 2.0
<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="/CustomerRecord">
<xsl:variable name="groups">
<xsl:for-each-group select="Customer" group-by="customer_number">
<group size="{count(current-group())}">
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</xsl:variable>
<Root>
<xsl:for-each select="$groups/group[#size > 1]">
<Customer_PO>
<xsl:copy-of select="Customer"/>
</Customer_PO>
</xsl:for-each>
<Customer_Faulty>
<xsl:copy-of select="$groups/group[#size = 1]/Customer"/>
</Customer_Faulty>
</Root>
</xsl:template>
</xsl:stylesheet>

Verticalize XML using XSLT

I am trying to implement an at first looking simple transformation but whatever I have tried has been failed.
The XML is generated from a fixed length record and have the below format.
<?xml version="1.0" encoding="UTF-8"?>
<record>
<no_of_records>30</no_of_records>
<cust_lastname_1>Smith</cust_lastname_1>
<cust_name_1>John</cust_name_1>
<cust_id_1>X45</cust_id_1>
<cust_lastname_2>George</cust_lastname_2>
<cust_name_2>Michael</cust_name_2>
<cust_id_2>X76</cust_id_2>
<cust_lastname_3>Ria</cust_lastname_3>
<cust_name_3>Chris</cust_name_3>
<cust_id_3>C87</cust_id_3>
...
</record>
The no_of_records indicates how many _X suffixed elements contains each record and because of its fix length origin has a defined maximum.
I want to transform it to a “verticalized” form resempling the below.
<record>
<customer num="1">
<lastname>Smith</lastname>
<name>John</name>
<id>X45</id>
</customer>
<customer num="2">
<lastname>George</lastname>
<name>Michael</name>
<id>X76</id>
</customer>
<customer num="3">
<lastname>Ria</lastname>
<name>Chris</name>
<id>C87</id>
...
</customer>
</record>
Any help would greatly appreciated.
In XSLT 2.0, you want something like
<xsl:for-each-group select="*" group-starting-with="*[starts-with(local-name(), 'cust_lastname']">
<customer num="{position()}">
<xsl:apply-templates select="current-group()"/>
</customer>
</xsl:for-each-group>
....
<xsl:template match="*[starts-with(local-name(), 'cust')]">
<xsl:element name="{replace(local-name(), 'cust_(.*?)_[0-9]+', '$1')}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
The solution from #Michael Kay works fine. Thank you !
XML
<?xml version="1.0" encoding="UTF-8"?>
<record>
<no_of_records>3</no_of_records>
<cust_lastname_1>Smith</cust_lastname_1>
<cust_name_1>John</cust_name_1>
<cust_id_1>X45</cust_id_1>
<cust_lastname_2>George</cust_lastname_2>
<cust_name_2>Michael</cust_name_2>
<cust_id_2>X76</cust_id_2>
<cust_lastname_3>Ria</cust_lastname_3>
<cust_name_3>Chris</cust_name_3>
<cust_id_3>C87</cust_id_3>
</record>
XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="record">
<records>
<xsl:for-each-group select="*[starts-with(local-name(), 'cust_')]"
group-starting-with="*[starts-with(local-name(), 'cust_lastname')]">
<customer num="{position()}">
<xsl:apply-templates select="current-group()"/>
</customer>
</xsl:for-each-group>
</records>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'cust')]">
<xsl:element name="{replace(local-name(), 'cust_(.*?)_[0-9]+', '$1')}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<records>
<customer num="1">
<lastname>Smith</lastname>
<name>John</name>
<id>X45</id>
</customer>
<customer num="2">
<lastname>George</lastname>
<name>Michael</name>
<id>X76</id>
</customer>
<customer num="3">
<lastname>Ria</lastname>
<name>Chris</name>
<id>C87</id>
</customer>
</records>

Merging two diff XMLS using XSLT(1.0)

I am new to XSLT and I need help in merging two different XML documents into one.
XML1.xml
<customers>
<customer>
<Person name="Ram" Id="101"/>
<address>flat 4</address>
</customer>
<customer>
<Person name="Raghav" Id="102"/>
<address>flat 9</address>
</customer>
</customers>
XML2.xml
<Products>
<Product>
<name>Onida Tv</name>
<consumer>Ram</consumer>
</Product>
<Product>
<name>washing machine</name>
<consumer>Ram</consumer>
</Product>
<Product>
<name>Water purifier</name>
<consumer>Raghav</consumer>
</Product>
<Product>
<name>iPhone</name>
<consumer>Raghav</consumer>
</Products>
</Products>
Desired XML output:
<customers>
<customer>
<Person name="Ram" Id="101"/>
<address>flat 4</address>
<products>
<name>washing machine</name>
<name>Onida TV</name>
</products>
</customer>
<customer>
<Person name="Raghav" Id="102"/>
<address>flat 9</address>
<products>
<name>iPhone</name>
<name>Water purifier</name>
</products>
</customer>
</customers>
The second XML is to be considered external in this context. I need to append to each customer the corresponding products. How can I do that?
Try it this way:
XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?>
<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:strip-space elements="*"/>
<xsl:variable name="lookup-document" select="document('XML2.xml')" />
<xsl:key name="product-by-consumer" match="Product" use="consumer" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="customer">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:variable name="name" select="Person/#name" />
<products>
<!-- switch context to the other document in order to use key -->
<xsl:for-each select="$lookup-document">
<xsl:copy-of select="key('product-by-consumer', $name)/name"/>
</xsl:for-each>
</products>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that this assumes that customer names are unique.

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.