user2051706
user2051706

Reputation: 75

xslt 1.0 grouping with compound keys (at different levels)

I have a transformation where I am trying to record a set of flattened transaction details and at the end of the file provide a summation of totals grouped by tender type and register number.
Recording the set of individual transaction details is the easy part and I have that working fine, but I am struggling with the summary portion.

The trouble is that I won’t know what or how many different register numbers there are, or what or how many tender types there are (so explicitly listing out summaries in the xslt with static filter strings is a no-go), so some sort of grouping seems to be in order.

One more wrench – I’m stuck using XSLT 1.0…

I tried messing around with the muenchian grouping, but between the compound key requirement (with register living at a different level than payment method) and my limited understanding on how the muenchian method and keys work in the first place I couldn’t seem to get it working, but I think it still may be the trick needed…

Any suggestions on how I might muenchia-magically get this to work?

here's an example source document:

<s0:SalesCollection xmlns:s0="http://mySourceSchema">
  <s0:Sale transactionnumber="1" register="1">
    <s0:Tender amount="1.11" paymentmethod="visa" />
    <s0:Tender amount="2.22" paymentmethod="mastercard" />
  </s0:Sale>
  <s0:Sale transactionnumber="2" register="1">
    <s0:Tender amount="5.55" paymentmethod="discover" />
    <s0:Tender amount="4.44" paymentmethod="visa" />
  </s0:Sale>
  <s0:Sale transactionnumber="1" register="2">
    <s0:Tender amount="9.99" paymentmethod="amex" />
    <s0:Tender amount="8.88" paymentmethod="visa" />
  </s0:Sale>
</s0:SalesCollection>

here's what I'm going for (again, I have the record[@type='detail'] records working already):

<ns0:root xmlns:ns0="http://myDestinationSchema">
  <ns0:record type="detail" transactionnumber="1" register="1" amount="1.11" paymentmethod="visa" />
  <ns0:record type="detail" transactionnumber="1" register="1" amount="2.22" paymentmethod="mastercard" />
  <ns0:record type="detail" transactionnumber="2" register="1" amount="5.55" paymentmethod="discover" />
  <ns0:record type="detail" transactionnumber="2" register="1" amount="4.44" paymentmethod="visa" />
  <ns0:record type="detail" transactionnumber="1" register="2" amount="9.99" paymentmethod="amex" />
  <ns0:record type="detail" transactionnumber="1" register="2" amount="8.88" paymentmethod="visa" />
  <ns0:record type="summary" register="1" amount="5.55" paymentmethod="visa" />
  <ns0:record type="summary" register="1" amount="2.22" paymentmethod="mastercard" />
  <ns0:record type="summary" register="1" amount="5.55" paymentmethod="discover" />
  <ns0:record type="summary" register="2" amount="9.99" paymentmethod="amex" />
  <ns0:record type="summary" register="2" amount="8.88" paymentmethod="visa" />
</ns0:root>

So how do I go about creating the summary records grouped by register and paymentmethod using xslt 1.0?

Upvotes: 2

Views: 1442

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243569

This transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:s0="http://mySourceSchema"
 xmlns:ns0="http://myDestinationSchema" exclude-result-prefixes="s0">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kTendByTypeAndReg" match="s0:Tender"
  use="concat(../@register, '#', @paymentmethod)"/>

 <xsl:template match="/*">
  <ns0:root xmlns:ns0="http://myDestinationSchema">
   <xsl:apply-templates select="*/*"/>
   <xsl:apply-templates mode="group" select=
   "*/*[generate-id()
       =generate-id(key('kTendByTypeAndReg',
                        concat(../@register, '#', @paymentmethod))[1]
                        )
       ]"/>
  </ns0:root>
 </xsl:template>

 <xsl:template match="s0:Tender">
   <ns0:record type="detail" transactionnumber="{../@transactionnumber}"
       register="{../@register}" amount="{@amount}" paymentmethod="{@paymentmethod}" />
 </xsl:template>

 <xsl:template match="*" mode="group">
    <ns0:record type="summary" register="{../@register}" paymentmethod="{@paymentmethod}"
  amount="{sum(key('kTendByTypeAndReg',concat(../@register,'#',@paymentmethod))
                   /@amount)}"/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<s0:SalesCollection xmlns:s0="http://mySourceSchema">
  <s0:Sale transactionnumber="1" register="1">
    <s0:Tender amount="1.11" paymentmethod="visa" />
    <s0:Tender amount="2.22" paymentmethod="mastercard" />
  </s0:Sale>
  <s0:Sale transactionnumber="2" register="1">
    <s0:Tender amount="5.55" paymentmethod="discover" />
    <s0:Tender amount="4.44" paymentmethod="visa" />
  </s0:Sale>
  <s0:Sale transactionnumber="1" register="2">
    <s0:Tender amount="9.99" paymentmethod="amex" />
    <s0:Tender amount="8.88" paymentmethod="visa" />
  </s0:Sale>
</s0:SalesCollection>

produces the wanted result:

<ns0:root xmlns:ns0="http://myDestinationSchema">
    <ns0:record type="detail" transactionnumber="1" register="1" amount="1.11" paymentmethod="visa"/>
    <ns0:record type="detail" transactionnumber="1" register="1" amount="2.22" paymentmethod="mastercard"/>
    <ns0:record type="detail" transactionnumber="2" register="1" amount="5.55" paymentmethod="discover"/>
    <ns0:record type="detail" transactionnumber="2" register="1" amount="4.44" paymentmethod="visa"/>
    <ns0:record type="detail" transactionnumber="1" register="2" amount="9.99" paymentmethod="amex"/>
    <ns0:record type="detail" transactionnumber="1" register="2" amount="8.88" paymentmethod="visa"/>
    <ns0:record type="summary" register="1" paymentmethod="visa" amount="5.55"/>
    <ns0:record type="summary" register="1" paymentmethod="mastercard" amount="2.22"/>
    <ns0:record type="summary" register="1" paymentmethod="discover" amount="5.55"/>
    <ns0:record type="summary" register="2" paymentmethod="amex" amount="9.99"/>
    <ns0:record type="summary" register="2" paymentmethod="visa" amount="8.88"/>
</ns0:root>

Explanation:

Proper use of:

  1. Muenchian Grouping method.

  2. AVT s (Attribute Value Templates).

Upvotes: 3

ABach
ABach

Reputation: 3738

When this XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:ns0="http://myDestinationSchema"
  xmlns:s0="http://mySourceSchema"
  exclude-result-prefixes="s0"
  version="1.0">
  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key
    name="kTenderByRegisterAndMethod"
    match="s0:Tender"
    use="concat(parent::*/@register, '+', @paymentmethod)"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="/*">
    <ns0:root>
      <xsl:apply-templates select="*/s0:Tender"/>
      <xsl:apply-templates 
        select="*/s0:Tender[generate-id() = 
                            generate-id(key(
                              'kTenderByRegisterAndMethod',
                              concat(parent::*/@register,
                                     '+',
                                     @paymentmethod))[1])]"
        mode="summary"/>
    </ns0:root>
  </xsl:template>

  <xsl:template match="s0:Tender">
    <ns0:record type="detail">
      <xsl:apply-templates select="parent::*/@*|@*"/>
    </ns0:record>
  </xsl:template>

  <xsl:template match="s0:Tender" mode="summary">
    <ns0:record type="summary" register="{parent::*/@register}">
      <xsl:attribute name="amount">
        <xsl:value-of
          select="sum(
                    key('kTenderByRegisterAndMethod',
                        concat(parent::*/@register,
                        '+', @paymentmethod))/@amount)"/>
      </xsl:attribute>
      <xsl:apply-templates select="@*[not(name() = 'amount')]"/>
    </ns0:record>
  </xsl:template>
</xsl:stylesheet>

...is applied to the original XML:

<s0:SalesCollection xmlns:s0="http://mySourceSchema">
  <s0:Sale transactionnumber="1" register="1">
    <s0:Tender amount="1.11" paymentmethod="visa"/>
    <s0:Tender amount="2.22" paymentmethod="mastercard"/>
  </s0:Sale>
  <s0:Sale transactionnumber="2" register="1">
    <s0:Tender amount="5.55" paymentmethod="discover"/>
    <s0:Tender amount="4.44" paymentmethod="visa"/>
  </s0:Sale>
  <s0:Sale transactionnumber="1" register="2">
    <s0:Tender amount="9.99" paymentmethod="amex"/>
    <s0:Tender amount="8.88" paymentmethod="visa"/>
  </s0:Sale>
</s0:SalesCollection>

...the wanted result is produced:

<ns0:root xmlns:ns0="http://myDestinationSchema">
  <ns0:record type="detail" transactionnumber="1" register="1" amount="1.11" paymentmethod="visa"/>
  <ns0:record type="detail" transactionnumber="1" register="1" amount="2.22" paymentmethod="mastercard"/>
  <ns0:record type="detail" transactionnumber="2" register="1" amount="5.55" paymentmethod="discover"/>
  <ns0:record type="detail" transactionnumber="2" register="1" amount="4.44" paymentmethod="visa"/>
  <ns0:record type="detail" transactionnumber="1" register="2" amount="9.99" paymentmethod="amex"/>
  <ns0:record type="detail" transactionnumber="1" register="2" amount="8.88" paymentmethod="visa"/>
  <ns0:record type="summary" register="1" amount="5.55" paymentmethod="visa"/>
  <ns0:record type="summary" register="1" amount="2.22" paymentmethod="mastercard"/>
  <ns0:record type="summary" register="1" amount="5.55" paymentmethod="discover"/>
  <ns0:record type="summary" register="2" amount="9.99" paymentmethod="amex"/>
  <ns0:record type="summary" register="2" amount="8.88" paymentmethod="visa"/>
</ns0:root>

Explanation

You were on the right track to examine Muenchian Grouping. Note the composite key I used that matches s0:Tender elements by concatenating their parent's @register value, a "+" sign (present for convenience and to ensure that the concatenation is never mangled), and their @paymentmethod value.

Stick with Muenchian Grouping, even if it's not the easiest concept to understand right off the bat. I, myself, learned by looking at Muenchian Grouping-related questions on SO and attempting to answer them; over time, it began to clear up for me.

Upvotes: 1

Related Questions