Multiply 2 filtered numbers and sum - xslt

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.

Related

XSLT: How to generate unique id for node based on value only

I have a source XML that contains address elements which could have the same values (please note that Contact/id=1 and Contact/id=3 have the same address:
<?xml version="1.0" encoding="utf-8"?>
<Contacts xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Contact>
<id>1</id>
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</Contact>
<Contact>
<id>2</id>
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</Contact>
<Contact>
<id>3</id>
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</Contact>
</Contacts>
Desired output with XSLT 1.0:
<?xml version="1.0" encoding="utf-8"?>
<Contacts xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Contact>
<id>1</id>
<Address>SomeId_1</Address>
</Contact>
<Contact>
<id>2</id>
<Address>SomeId_2</Address>
</Contact>
<Contact>
<id>3</id>
<Address>SomeId_1</Address>
</Contact>
</Contacts>
When I used function generate-id(Address) I got different id for addresses in Contact 1 and Contact 3. What other way to generate unique id for node based on its value only?
Thank you for the help.
I would advise building a key of values as a lookup table and then just orienting from the first entry of the lookup table for the unique number:
t:\ftemp>type ivan.xml
<?xml version="1.0" encoding="utf-8"?>
<Contacts xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Contact>
<id>1</id>
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</Contact>
<Contact>
<id>2</id>
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</Contact>
<Contact>
<id>3</id>
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</Contact>
</Contacts>
t:\ftemp>call xslt ivan.xml ivan.xsl
<?xml version="1.0" encoding="utf-8"?><Contacts xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Contact>
<id>1</id>
<Address>SomeId_1</Address>
</Contact>
<Contact>
<id>2</id>
<Address>SomeId_2</Address>
</Contact>
<Contact>
<id>3</id>
<Address>SomeId_1</Address>
</Contact>
</Contacts>
t:\ftemp>type ivan.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:key name="city-pc-pair" match="Address"
use="concat(City/#City,'
',Postcode/#PostCode)"/>
<xsl:template match="Address">
<xsl:for-each select="key('city-pc-pair',
concat(City/#City,'
',Postcode/#PostCode))[1]">
<Address>SomeId_<xsl:number level="any"/></Address>
</xsl:for-each>
</xsl:template>
<xsl:template match="#*|node()"><!--identity for all other nodes-->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem Done!
As for the concatenation that I'm using, I tell my students the technique of using a carriage return as a field delimiter reduces the likelihood of an unintended value collision to an infinitesimal size since there are very few hard carriage returns in XML content (those carriage returns that are parts of end-of-line sequences are normalized to a line-feed and so do not appear in the data).
Edited to add the following entity technique that may improve maintenance since it focuses the lookup expression to a single declaration in the stylesheet, so as not to be accidentally written differently in two different parts of the stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet
[
<!ENTITY lookup "concat(City/#City,'
',Postcode/#PostCode)">
]>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:key name="city-pc-pair" match="Address" use="&lookup;"/>
<xsl:template match="Address">
<xsl:for-each select="key('city-pc-pair',&lookup;)[1]">
<Address>SomeId_<xsl:number level="any"/></Address>
</xsl:for-each>
</xsl:template>
<xsl:template match="#*|node()"><!--identity for all other nodes-->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XSLT's generate-id() is intended for generating #xml:id's, which are generally attributes meant to uniquely identify a node in the document. So everytime you call generate-id(), you
should be getting a unique value.
The identifier's you want to generate are just data, and have nothing to do with what generate-id() does.
If you want an identifier whose value is based on the value of some other data, then you
should just generate it from that data. Concat those values together, for example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="Address">
<Address>
<xsl:value-of select="concat(City/#City, '+', Postcode/#Postcode)"/>
</Address>
</xsl:template>
Will produce:
<?xml version="1.0" encoding="UTF-8"?>
<Contacts xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Contact>
<id>1</id>
<Address>Wien+LSP-123</Address>
</Contact>
<Contact>
<id>2</id>
<Address>Toronto+LKT-947</Address>
</Contact>
<Contact>
<id>3</id>
<Address>Wien+LSP-123</Address>
</Contact>
</Contacts>
If you have some other requirements for the identifier, then you can write a function
or use a lookup table to map from those keys to some other identifiers.

Get distinct values from xml

My sample xml looks below: I need to get the distinct states from xml. I am using xslt 1.0 in vs 2010 editor.
<?xml version="1.0" encoding="utf-8" ?>
<states>
<node>
<value>2</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>NJ</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
</states>
My xslt looks like below:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:user="urn:my-scripts">
<xsl:output method="text" indent="yes"/>
<xsl:key name="st" match="//states/node/state" use="." />
<xsl:variable name="disst">
<xsl:for-each select="//states/node[contains(value,1)]/state[generate-id()=generate-id(key('st',.)[1])]" >
<xsl:choose>
<xsl:when test="(position() != 1)">
<xsl:value-of select="concat(', ',.)" disable-output-escaping="yes"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/" >
<xsl:value-of disable-output-escaping="yes" select="$disst"/>
</xsl:template>
</xsl:stylesheet>
Output: DE,NJ,NY
My above xml looks good for the above test xml.
If I change the xml as below:
<?xml version="1.0" encoding="utf-8" ?>
<states>
<node>
<value>2</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>NJ</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
</states>
It in not picking the state DE. Can any one suggest the suitable solution.Thanks in advance.
I need to find out the distinct states from the xml.
The problem here is your use of a predicate in your Muenchian grouping XPath:
[contains(value,1)]
This will often make Muenchian grouping fail to find all of the available distinct values. Instead, you should add the predicate to the key:
<xsl:key name="st" match="//states/node[contains(value, 1)]/state" use="." />
Alternatively, you can apply the predicate inside the grouping statement:
<xsl:apply-templates
select="//states/node
/state[generate-id() =
generate-id(key('st',.)[contains(../value, 1)][1])]" />
Full XSLT (with some improvements):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:user="urn:my-scripts">
<xsl:output method="text" indent="yes"/>
<xsl:key name="st" match="//states/node/state" use="." />
<xsl:variable name="a" select="1" />
<xsl:variable name="disst">
<xsl:apply-templates
select="//states/node
/state[generate-id() =
generate-id(key('st',.)[contains(../value, $a)][1])]" />
</xsl:variable>
<xsl:template match="state">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select ="." disable-output-escaping="yes" />
</xsl:template>
<xsl:template match="/" >
<xsl:value-of disable-output-escaping="yes" select="$disst"/>
</xsl:template>
</xsl:stylesheet>
Result when run on your sample XML:
DE,NJ,NY

XSLT: iteration on string with indexes

I am having a trouble with very easy XSLT transformation. Let's assume that this is the input XML document:
<root>
<myString>ABBCD</myString>
</root>
The output XML should be:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
Is there an easy way to split such string and increment index over it?
It's pretty easy in XSLT 2.0...
XML Input
<root>
<myString>ABBCD</myString>
</root>
XSLT 2.0
<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="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="myString">
<myCharacters>
<xsl:analyze-string select="." regex=".">
<xsl:matching-substring>
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="."/></value>
</character>
</xsl:matching-substring>
</xsl:analyze-string>
</myCharacters>
</xsl:template>
</xsl:stylesheet>
XML Output
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
It's not terrible in 1.0 either. You can use a recursive template. The following will produce the same output:
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="myString">
<myCharacters>
<xsl:call-template name="analyzeString">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</myCharacters>
</xsl:template>
<xsl:template name="analyzeString">
<xsl:param name="pos" select="1"/>
<xsl:param name="string"/>
<character>
<id><xsl:value-of select="$pos"/></id>
<value><xsl:value-of select="substring($string,1,1)"/></value>
</character>
<xsl:if test="string-length($string)>=2">
<xsl:call-template name="analyzeString">
<xsl:with-param name="pos" select="$pos+1"/>
<xsl:with-param name="string" select="substring($string,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I. XSLT 1.0 Solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vStyle" select="document('')"/>
<xsl:template match="myString">
<xsl:variable name="vStr" select="."/>
<root>
<myCharacters>
<xsl:for-each select=
"($vStyle//node()|$vStyle//#*|$vStyle//namespace::*)
[not(position() > string-length($vStr))]">
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="substring($vStr,position(),1)"/></value>
</character>
</xsl:for-each>
</myCharacters>
</root>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<myString>ABBCD</myString>
</root>
the wanted, correct result is produced:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
Alternatively, with FXSL one can use the str-map template like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:testmap="testmap" xmlns:f="http://fxsl.sf.net/"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="xsl f ext testmap">
<xsl:import href="str-dvc-map.xsl"/>
<testmap:testmap/>
<xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="myString">
<xsl:variable name="vrtfChars">
<xsl:call-template name="str-map">
<xsl:with-param name="pFun" select="$vTestMap"/>
<xsl:with-param name="pStr" select="."/>
</xsl:call-template>
</xsl:variable>
<myCharacters>
<xsl:apply-templates select="ext:node-set($vrtfChars)/*"/>
</myCharacters>
</xsl:template>
<xsl:template name="enumChars" match="*[namespace-uri() = 'testmap']"
mode="f:FXSL">
<xsl:param name="arg1"/>
<character>
<value><xsl:value-of select="$arg1"/></value>
</character>
</xsl:template>
<xsl:template match="character">
<character>
<id><xsl:value-of select="position()"/></id>
<xsl:copy-of select="*"/>
</character>
</xsl:template>
</xsl:stylesheet>
to produce the same correct result:
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
II. XSLT 2.0 solution -- shorter and simpler than other answers:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="myString">
<root>
<myCharacters>
<xsl:for-each select="string-to-codepoints(.)">
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="codepoints-to-string(.)"/></value>
</character>
</xsl:for-each>
</myCharacters>
</root>
</xsl:template>
</xsl:stylesheet>
when applied on the same XML document (above), produces the wanted, correct result:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>

XSLT merging/concatenating values of siblings nodes of same name into single node

Input xml
<catalog>
<product id="1">
<name>abc</name>
<category>aaa</category>
<category>bbb</category>
<category>ccc</category>
</product>
<product id="2">
<name>cde</name>
<category>aaa</category>
<category>bbb</category>
</product>
</catalog>
Expected Output xml
<products>
<product>
<id>1</id>
<name>abc</name>
<category>aaa,bbb,ccc</category>
</product>
<product>
<id>2</id>
<name>cde</name>
<category>aaa,bbb</category>
</product>
</products>
XSLT for transformation
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/catalog">
<products>
<xsl:for-each select="product">
<product>
<id><xsl:value-of select="#id"/></id>
<name><xsl:value-of select="name"/></name>
<category><xsl:value-of select="category" /></category>
</product>
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
Actual Output xml :(
<products>
<product>
<id>1</id>
<name>abc</name>
<category>aaa</category>
</product>
<product>
<id>2</id>
<name>cde</name>
<category>aaa</category>
</product>
</products>
Code needed in looping through all sibling node by the name 'category' under every 'product' and merging/concatenating into single node separated by a comma. Number of 'category' varies for every product and hence the count is unknown.
Using this handy join call-template defined here, this becomes as simple as:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/catalog">
<products>
<xsl:for-each select="product">
<product>
<id>
<xsl:value-of select="#id"/>
</id>
<name>
<xsl:value-of select="name"/>
</name>
<category>
<xsl:call-template name="join">
<xsl:with-param name="list" select="category" />
<xsl:with-param name="separator" select="','" />
</xsl:call-template>
</category>
</product>
</xsl:for-each>
</products>
</xsl:template>
<xsl:template name="join">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="$separator" />
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
<products>
<product>
<id>1</id>
<name>abc</name>
<category>aaa,bbb,ccc</category>
</product>
<product>
<id>2</id>
<name>cde</name>
<category>aaa,bbb</category>
</product>
</products>
In XSLT 2.0 you only need to make one small change to your code:
<category><xsl:value-of select="category" separator=","/></category>
Note that if you require an XSLT 1.0 solution it's a good idea to say so. Some people in some environments are stuck on 1.0, but a lot of people aren't.
Here's one other XSLT 1.0 solution.
When this XSLT:
<?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:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="product">
<xsl:copy>
<xsl:apply-templates select="*[not(self::category)]" />
<category>
<xsl:apply-templates select="category/text()" />
</category>
</xsl:copy>
</xsl:template>
<xsl:template match="category/text()">
<xsl:if test="position() > 1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
...is applied to the OP's original XML:
<catalog>
<product id="1">
<name>abc</name>
<category>aaa</category>
<category>bbb</category>
<category>ccc</category>
</product>
<product id="2">
<name>cde</name>
<category>aaa</category>
<category>bbb</category>
</product>
</catalog>
...the desired result is produced:
<?xml version="1.0"?>
<catalog>
<product>
<name>abc</name>
<category>aaa,bbb,ccc</category>
</product>
<product>
<name>cde</name>
<category>aaa,bbb</category>
</product>
</catalog>
Explanation:
The first template -- the Identity Template -- matches all nodes and attributes and copies them to the result document as-is.
The second template overrides the Identity Template by creating a new <category> element and processing the text children of each <category> element in the current location of the document.
The final template outputs the text values and commas as necessary.

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.