Link to home
Start Free TrialLog in
Avatar of tesmc
tesmcFlag for United States of America

asked on

XSL: How to group and sum into one node

I have the following input:

<root>
	<CollectMiscFeeRQ>
		<Payment xmlns="http://services.sabre.com/STL/v01">
			<Amount currencyCode="MXN">549</Amount>
			<FormOfPayment>
				<Cash/>
			</FormOfPayment>
		</Payment>
		<Payment xmlns="http://services.sabre.com/STL/v01">
			<Amount currencyCode="MXN">549</Amount>
			<FormOfPayment>
				<Cash/>
			</FormOfPayment>
		</Payment>
		<Payment xmlns="http://services.sabre.com/STL/v01">
			<Amount currencyCode="MXN">549</Amount>
			<FormOfPayment>
				<Cash/>
			</FormOfPayment>
		</Payment>
	</CollectMiscFeeRQ>
</root>

Open in new window



I want to group by FOP so that my output sums the total:

	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">1647</Amount>
		<FormOfPayment>
			<Cash/>
		</FormOfPayment>
	</Payment>

Open in new window




I was trying to do something like this but it failed:

  <xsl:key name="paymentCash" match="stl:Payment" use="stl:FormOfPayment/stl:Cash"/>


      <xsl:for-each select="stl:Payment[stl:FormOfPayment/stl:Cash and generate-id(.)=generate-id(key('paymentCash', stl:FormOfPayment/stl:Cash)[1])]" >
....

but it would never enter the for-each clause.
Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

It is a bit unclear what you are trying to do.
Do you need to group on the form of payment?
Meaning there would be other forms of payment expressed in the element name, for example
<FormOfPayment>
			<CreditCard/>
		</FormOfPayment>

Open in new window

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:stl="http://services.sabre.com/STL/v01"
    version="1.0">
    
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>
    
    <xsl:key name="paymentCash" match="stl:Payment" use="local-name(stl:FormOfPayment/*)"/>
    
    <xsl:template match="node()">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="stl:Payment[not(stl:FormOfPayment/stl:Cash)]"></xsl:template>
    <xsl:template match="stl:Payment[not(generate-id() = generate-id(key('paymentCash', 'Cash')[1]))]"/>
    
    <xsl:template match="stl:Payment/stl:Amount">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:value-of select="sum(key('paymentCash', 'Cash')/stl:Amount)"/>
        </xsl:copy>
    </xsl:template>
    
    
    
</xsl:stylesheet>

Open in new window

As you can see I use a key based on the name rather than the element (that is I hope I got the requirements right)

You don't need grouping, just phase out the Payments you don't need
- all non cash payments
- all cash payments except the first
(two templates for that)

And calculate the total amount at the level of the amount
(it will only reach the amount once, in the first cash payment)

This way you use the power of the template rules to its maximum, no for each, no if or when, clean maintainable code

This works if you don't want to do something with other payment types
(if there is only one extra payment type, known from the schema, just add another template,
if there are many or if the schema is loose, go for grouping)

If you use XSLT2, use a different type of grouping using for-each-group

Note that if you need to deal with totals on different currencies, you will need grouping

The heart of the issue was the key not looking for the payment type element name.

You can build on that

I gave you the best solution possible with the very limited info you gave... clear specs always help
Avatar of tesmc

ASKER

@Gertone (Geert Bormans) - yes - need to group on the form of payment.
yes - there would be other forms of payment expressed in the element name. in fact there could be duplicate instances of credit card and those need to be grouped together accordingly.

today i'm grouping and summing amount by credit card successfully it's just for cash that my code fails.
This is what i have for credit card:

  <xsl:key name="paymentKey"  match="stl:Payment" use="stl:FormOfPayment/stl:CreditCard/stl:Number"/>

      <!-- group by distinct CreditCard/Number -->
      <xsl:for-each select="stl:Payment[stl:FormOfPayment/stl:CreditCard/stl:Number and generate-id(.)=generate-id(key('paymentKey', stl:FormOfPayment/stl:CreditCard/stl:Number)[1])]" >

...

Open in new window

OK, so here you group on the creditcard number.
Could you post a less obvious XML that is closer to reality
(different methods of payment, different currencies if you have them, multiple cards, type of cards...)
and handcraft the expected result

Your last message shows it is more complex, but it does not give a clue what you have to do with the complexity

Anyhow, the way I use the key in my first example should hint you towards a solution
Avatar of tesmc

ASKER

I could have the following input:

<CollectMiscFeeRQ>
	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">200</Amount>
		<FormOfPayment>
			<CreditCard>
				<Code>CA</Code>
				<Number>5555444433331111</Number>
				<ExpiryDate>1020</ExpiryDate>
				<ApprovalCode>36152</ApprovalCode>
			</CreditCard>
		</FormOfPayment>
	</Payment>
	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">200</Amount>
		<FormOfPayment>
			<CreditCard>
				<Code>CA</Code>
				<Number>5555444433331111</Number>
				<ExpiryDate>1020</ExpiryDate>
				<ApprovalCode>36152</ApprovalCode>
			</CreditCard>
		</FormOfPayment>
	</Payment>
	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">200</Amount>
		<FormOfPayment>
			<CreditCard>
				<Code>CA</Code>
				<Number>5555444433331111</Number>
				<ExpiryDate>1020</ExpiryDate>
				<ApprovalCode>36152</ApprovalCode>
			</CreditCard>
		</FormOfPayment>
	</Payment>
	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">100</Amount>
		<FormOfPayment>
			<Cash/>
		</FormOfPayment>
	</Payment>
	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">100</Amount>
		<FormOfPayment>
			<Cash/>
		</FormOfPayment>
	</Payment>
	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">100</Amount>
		<FormOfPayment>
			<Cash/>
		</FormOfPayment>
	</Payment>
</CollectMiscFeeRQ>

Open in new window



and this is expected output:
<CollectMiscFeeRQ>
	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">600</Amount>
		<FormOfPayment>
			<CreditCard>
				<Code>CA</Code>
				<Number>5555444433331111</Number>
				<ExpiryDate>1020</ExpiryDate>
				<ApprovalCode>36152</ApprovalCode>
			</CreditCard>
		</FormOfPayment>
	</Payment>
	<Payment xmlns="http://services.sabre.com/STL/v01">
		<Amount currencyCode="MXN">300</Amount>
		<FormOfPayment>
			<Cash/>
		</FormOfPayment>
	</Payment>
</CollectMiscFeeRQ>

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Added a second key

I do a first level grouping to differentiate between cash and card

For the card payments I do a second level grouping for each card number

Rest flows nicely through the templates

If you have done Muenchian Grouping before, I guess this should be clear
Avatar of tesmc

ASKER

thank you for your help.