Search code examples
xmlxsltmuenchian-grouping

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?


Solution

  • 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).