Inline xslt for max values per duplicate record - xslt

I need to map the following input message instance in my biztalk project so that for every group of customer records the program checks if there are any duplicate debtor info values. If there are duplicate values the Inline XSLT template checks max value of the total sum in repeating records and changes destination invoice info code to value "Al".
Input:
<customer>
<record>
<Debtor>
<Debtor_info1>
<name>Jack</name>
</Debtor_info1>
<Debtor_info2>
<y-value>0123-xxx</y-value>
<postnumber>Nevada 666</postnumber>
</Debtor_info2>
</Debtor>
<invoice_info>
<code></code>
<invoice_address></invoice_address>
<total_amount>6000</total_amount>
</invoice_info>
</record>
<record>
<Debtor>
<Debtor_info1>
<name>Jack</name>
</Debtor_info1>
<Debtor_info2>
<y-value>0123-xxx</y-value>
<postnumber>Nevada 666</postnumber>
</Debtor_info2>
</Debtor>
<invoice_info>
<code></code>
<invoice_address></invoice_address>
<total_amount>3000</total_amount>
</invoice_info>
</record>
<record>
<Debtor>
<Debtor_info1>
<name>Evelyn</name>
</Debtor_info1>
<Debtor_info2>
<y-value>0123-xxx</y-value>
<postnumber>Manchester 666</postnumber>
</Debtor_info2>
</Debtor>
<invoice_info>
<code></code>
<invoice_address></invoice_address>
<total_amount>30</total_amount>
</invoice_info>
</record></customer>
Output:
<customer>
<record>
<Debtor>
<Debtor_info1>
<name>Jack</name>
</Debtor_info1>
<Debtor_info2>
<y-value>0123-xxx</y-value>
<postnumber>Nevada 666</postnumber>
</Debtor_info2>
</Debtor>
<invoice_info>
<code>Al</code>
<invoice_address></invoice_address>
<total_amount>6000</total_amount>
</invoice_info>
</record>
<record>
<Debtor>
<Debtor_info1>
<name>Jack</name>
</Debtor_info1>
<Debtor_info2>
<y-value>0123-xxx</y-value>
<postnumber>Nevada 666</postnumber>
</Debtor_info2>
</Debtor>
<invoice_info>
<code></code>
<invoice_address></invoice_address>
<total_amount>3000</total_amount>
</invoice_info>
</record>
<record>
<Debtor>
<Debtor_info1>
<name>Evelyn</name>
</Debtor_info1>
<Debtor_info2>
<y-value>0123-xxx</y-value>
<postnumber>Manchester 666</postnumber>
</Debtor_info2>
</Debtor>
<invoice_info>
<code></code>
<invoice_address></invoice_address>
<total_amount>30</total_amount>
</invoice_info>
</record></customer>
I have tried achieving this using Muenchian grouping (XSLT 1.0) with one xslt call template and one inline xslt template connected to destination schema as well as other methods with no luck whatsoever. The inline template should use sequence of records to sort and output max value of the total_amount using debtor info values, but so far I haven't been able to achieve desired output. I would really appreciate help in this issue.

I suppose this is one way you could look at it:
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:strip-space elements="*"/>
<xsl:key name="dup" match="invoice_info" use="../Debtor/Debtor_info1" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="invoice_info[count(key('dup', ../Debtor/Debtor_info1)) > 1]">
<xsl:variable name="max-amt">
<xsl:for-each select="key('dup', ../Debtor/Debtor_info1)">
<xsl:sort select="total_amount" data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="total_amount"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<xsl:copy>
<xsl:choose>
<xsl:when test="total_amount = $max-amt">
<code>A1</code>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="code"/>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates select="invoice_address | total_amount"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that in case of a tie, all records having the max value will be marked.

Related

Merge two xml files based on key field

I have requirement two merge two xml files. Based on the key field in the files I want to merge entire content of the xml 1 to xml 2.
Could you please help me to achieve this scenario.
I have tried this xslt but I am getting below error.
"a sequence of more than one item is not allowed as the first argument of fn:parse-xml()"
XSLT code.
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:mode on-no-match="shallow-copy" />
<xsl:param name="XML1" />
<xsl:variable name="details-doc" select="parse-xml($XML1)" />
<xsl:template match="Key">
<xsl:copy-of select="." />
<xsl:variable name="Key" select="string(.)" />
<xsl:copy-of select="$details-doc/Record/Detail[Key = $Key]" />
</xsl:template>
</xsl:stylesheet>
XML1.
<Record>
<Detail>
<Key>1</Key>
<Place>Ocean</Place>
<City>Urban</City>
</Detail>
<Detail>
<Key>2</Key>
<Place>Road</Place>
<City>Rural</City>
</Detail>
<Detail>
<Key>3</Key>
<Place>Plane</Place>
<City>Semiurban</City>
</Detail>
</Record>
XML2
<Record>
<Contact>
<Key>1</Key>
<Name>Jack</Name>
</Contact>
<Contact>
<Key>2</Key>
<Name>Ethan</Name>
</Contact>
<Contact>
<Key>3</Key>
<Name>Ron</Name>
</Contact>
</Record>
And the expected output.
<Record>
<Contact>
<Key>1</Key>
<Name>Jack</Name>
<Place>Ocean</Place>
<City>Urban</City>
</Contact>
<Contact>
<Key>2</Key>
<Name>Ethan</Name>
<Place>Road</Place>
<City>Rural</City>
</Contact>
<Contact>
<Key>3</Key>
<Name>Ron</Name>
<Place>Plane</Place>
<City>Semiurban</City>
</Contact>
</Record>

XSLT logic to add a sequence for the combination of elements with same dates and ID field

I am struggling to create logic for transformation.
Logic: "Seq" A sequential number used to make a unique key when the ID and Date fields are equal.
<root>
<Record>
<ID>11</ID>
<date>2020-03-11-07:00</date>
<quantity>10</quantity>
</Record>
<Record>
<ID>13</ID>
<date>2020-03-12-07:00</date>
<quantity>20</quantity>
</Record>
<Record>
<ID>15</ID>
<date>2020-03-13-07:00</date>
<quantity>40</quantity>
</Record>
<Record>
<ID>11</ID>
<date>2020-03-11-07:00</date>
<quantity>5</quantity>
</Record>
<Record>
<ID>13</ID>
<date>2020-03-17-07:00</date>
<quantity>100</quantity>
</Record>
</root>
to, Output
ID,seq,Date,quantity
11,1,2020-03-11-07:00,10
11,2,2020-03-11-07:00,5
13,1,2020-03-12-07:00,20
15,1,2020-03-13-07:00,40
13,1,2020-03-17-07:00,100
In XSLT 3 it is a simple grouping problem with a composite key:
<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 method="text" />
<xsl:template match="root">
<xsl:text>ID,seq,Date,quantity
</xsl:text>
<xsl:for-each-group select="Record" composite="yes" group-by="ID, date">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="Record">
<xsl:value-of select="ID, position(), date, quantity" separator=","/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/93dFepy
XSLT 3 can be used with Saxon 9.8 and later or AltovaXML 2017 R3 or later.

How to resolve Group and Sort withing XSLT

I have the following input message where the data needs to be grouped based on 'EmpNo' and 'Date' first. But the result should be taken from the latest date time from field 'lastDateTime'
Input XML:
<Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>3</Hours>
<EmpNo>825</EmpNo>
<lastDateTime>2019-04-12T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>4</Hours>
<EmpNo>826</EmpNo>
<lastDateTime>2019-04-12T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>5</Hours>
<EmpNo>827</EmpNo>
<lastDateTime>2019-04-12T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>3</Hours>
<EmpNo>825</EmpNo>
<lastDateTime>2019-04-14T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>4</Hours>
<EmpNo>826</EmpNo>
<lastDateTime>2019-04-10T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>5</Hours>
<EmpNo>827</EmpNo>
<lastDateTime>2019-04-12T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>3</Hours>
<EmpNo>825</EmpNo>
<lastDateTime>2019-04-10T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>4</Hours>
<EmpNo>826</EmpNo>
<lastDateTime>2019-04-11T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>5</Hours>
<EmpNo>827</EmpNo>
<lastDateTime>2019-04-16T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
</Record>
There could be many EmpNo with different Dates as well in the Input XML file. This needs to be grouped but it requires to extract only that particular node where lastDateTime is the latest.
Expected Result:
<Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>6</Hours>
<EmpNo>825</EmpNo>
<lastDateTime>2019-04-14T08:35:38.000</lastDateTime>
<Rate>1142</Rate>
<Code>13</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>4</Hours>
<EmpNo>826</EmpNo>
<lastDateTime>2019-04-12T08:35:38.000</lastDateTime>
<Rate>1140</Rate>
<Code>11</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>11</Hours>
<EmpNo>827</EmpNo>
<lastDateTime>2019-04-16T08:35:38.000</lastDateTime>
<Rate>1147</Rate>
<Code>18</Code>
</Record>
</Record>
I tried to write the following code but no luck.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf" exclude-result-prefixes="#all" version="2.0">
<xsl:output method="xml" encoding="utf-8" indent="no"/>
<xsl:template match="Record">
<xsl:copy>
<xsl:for-each-group select="Record" group-by="EmpNo">
<xsl:for-each-group select="current-group()" group-by="Date">
<xsl:copy>
<EmpNo>
<xsl:value-of select="EmpNo"/>
</EmpNo>
<Date>
<xsl:value-of select="Date"/>
</Date>
<lastDateTime>
<xsl:value-of select="max(current-group()/lastDateTime/xs:dateTime(.))"/>
</lastDateTime>
<Hours>
<xsl:value-of select="Hours[Date=max(current-group()/lastDateTime/xs:dateTime(.))]"/>
</Hours>
</xsl:copy>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
Based on your description I think you want
<xsl:template match="Record">
<xsl:copy>
<xsl:for-each-group select="Record" group-by="EmpNo">
<xsl:for-each-group select="current-group()" group-by="Date">
<xsl:for-each select="current-group()">
<xsl:sort select="xs:dateTime(lastDateTime)"/>
<xsl:if test="position() = last()">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
However, result at https://xsltfiddle.liberty-development.net/ncdD7mB is
<Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>3</Hours>
<EmpNo>825</EmpNo>
<lastDateTime>2019-04-14T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>4</Hours>
<EmpNo>826</EmpNo>
<lastDateTime>2019-04-12T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
<Record>
<Date>2019-04-01T00:00:00.000</Date>
<Hours>5</Hours>
<EmpNo>827</EmpNo>
<lastDateTime>2019-04-16T08:35:38.000</lastDateTime>
<Rate>1139</Rate>
<Code>102486</Code>
</Record>
</Record>
I am not sure from which item in a group you want to take the not grouped child elements.

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>

XSLT Sorting with first letter of family name

I am trying to modify the following xsl so that the output matches the desired output shown below. I am trying to sort the records alphabetically by the first name of the family Initial. However at the moment it only sorts by the creators initial I need it to include the editors initial where the record does not have a creators element.
XML:
<records>
<record>
<creators>
<item>
<name>
<family>Smith</family>
<given>Tim</given>
</name>
</item>
</creators>
</record>
<record>
<creators>
<item>
<name>
<family>Lambert</family>
<given>John</given>
</name>
</item>
</creators>
<editors>
<item>
<name>
<family>testEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
<record>
<editors>
<item>
<name>
<family>ZambertEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
XSL:
<xsl:key name="initial" match="record" use="substring(creators/item/name/family,1,1)"/>
<xsl:template match="/">
<xsl:for-each select="//record[generate-id(.)= generate-id(key('initial', substring(creators/item/name/family,1,1))[1])]">
<xsl:sort select="substring(creators/item/name/family,1,1)"/>
<xsl:for-each select="key('initial', substring(creators/item/name/family,1,1))">
<xsl:if test="position() = 1">
<br /><h3 class="border">
<xsl:value-of select="substring(creators/item/name/family,1,1)"/>
</h3>
</xsl:if>
<p>
<xsl:value-of select="creators/item/name/family"/>
</p>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
Desired output:
L
Lambert
S
Smith
Z
ZambertEDITOR
This is a simple grouping problem:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kWith1stLetter" match="family" use="substring(.,1,1)"/>
<xsl:template match="/*">
<xsl:apply-templates select=
"*/*/*/*/family
[generate-id()
=
generate-id(key('kWith1stLetter',substring(.,1,1))[1])
]">
<xsl:sort select="substring(.,1,1)" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match= "family">
<h3 class="border">
<xsl:value-of select="substring(.,1,1)"/>
</h3>
<xsl:apply-templates mode="inGroup"
select="key('kWith1stLetter',substring(.,1,1))"/>
</xsl:template>
<xsl:template match="family" mode="inGroup">
<p><xsl:value-of select="."/></p>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<records>
<record>
<creators>
<item>
<name>
<family>Smith</family>
<given>Tim</given>
</name>
</item>
</creators>
</record>
<record>
<creators>
<item>
<name>
<family>Lambert</family>
<given>John</given>
</name>
</item>
</creators>
<editors>
<item>
<name>
<family>testEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
<record>
<editors>
<item>
<name>
<family>ZambertEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
</records>
the wanted, correct result is produced:
<h3 class="border">L</h3>
<p>Lambert</p>
<h3 class="border">S</h3>
<p>Smith</p>
<h3 class="border">t</h3>
<p>testEDITOR</p>
<h3 class="border">Z</h3>
<p>ZambertEDITOR</p>
and it is displayed by the browser as:
L
Lambert
S
Smith
t
testEDITOR
Z
ZambertEDITOR
Explanation:
Proper use of the Muenchian Grouping Method.