Question

We're aiming to print invoice lines grouped first by type of service being invocied and then by job description and constultant names, in this manner:

CONSULTANCY

Doing Actual Work
Gummo - 2014-03-03
Zeppo - 2014-02-24
Harpo - 2014-03-07

Snide Remarks
Groucho - 2014-02-24

Harp Playing
Harpo - 2014-02-28

EXPENSES INCURRED

Cigars

We're extracting this data from an xml file with a somewhat complex structure, namely the national Danish OIOUBL standard:

<Invoice>

(...)

<cac:InvoiceLine>
    <cbc:ID>1</cbc:ID>
    <cbc:Note>Bla bla</cbc:Note>
    <cbc:InvoicedQuantity unitCode="EA">1.2500</cbc:InvoicedQuantity>
    <cbc:LineExtensionAmount currencyID="DKK">1395.0000</cbc:LineExtensionAmount>
    <cac:Delivery>
      <cbc:ActualDeliveryDate>2014-02-24</cbc:ActualDeliveryDate>
    </cac:Delivery>
    <cac:TaxTotal>
      <cbc:TaxAmount currencyID="DKK">348.75</cbc:TaxAmount>
      <cac:TaxSubtotal>
        <cbc:TaxableAmount currencyID="DKK">1395.00</cbc:TaxableAmount>
        <cbc:TaxAmount currencyID="DKK">348.75</cbc:TaxAmount>
        <cac:TaxCategory>
          <cbc:ID schemeAgencyID="320" schemeID="urn:oioubl:id:taxcategoryid-1.1">StandardRated</cbc:ID>
          <cbc:Percent>25.00</cbc:Percent>
          <cac:TaxScheme>
            <cbc:ID schemeAgencyID="320" schemeID="urn:oioubl:id:taxschemeid-1.1">63</cbc:ID>
            <cbc:Name>Moms</cbc:Name>
          </cac:TaxScheme>
        </cac:TaxCategory>
      </cac:TaxSubtotal>
    </cac:TaxTotal>
    <cac:Item>
      <cbc:Description>Snide Remarks</cbc:Description>
      <cbc:Name>CONSULTANCY</cbc:Name>
      <cac:SellersItemIdentification>
        <cbc:ID schemeAgencyID="9" schemeID="foo">Groucho</cbc:ID>
        <cbc:ExtendedID>Timer</cbc:ExtendedID>
      </cac:SellersItemIdentification>
      <cac:StandardItemIdentification>
        <cbc:ID schemeAgencyID="9" schemeID="foo">2014-02-24</cbc:ID>
      </cac:StandardItemIdentification>
      <cac:AdditionalItemIdentification>
        <cbc:ID schemeAgencyID="9" schemeID="foo">10000</cbc:ID>
      </cac:AdditionalItemIdentification>
    </cac:Item>
    <cac:Price>
      <cbc:PriceAmount currencyID="DKK">1116.0000</cbc:PriceAmount>
      <cbc:BaseQuantity unitCode="EA">1</cbc:BaseQuantity>
    </cac:Price>
  </cac:InvoiceLine>

(...)

  <cac:InvoiceLine>
    <cbc:ID>6</cbc:ID>
    <cbc:InvoicedQuantity unitCode="EA">1.0000</cbc:InvoicedQuantity>
    <cbc:LineExtensionAmount currencyID="DKK">1116.0000</cbc:LineExtensionAmount>
    <cac:Delivery>
      <cbc:ActualDeliveryDate>2014-03-06</cbc:ActualDeliveryDate>
    </cac:Delivery>
    <cac:TaxTotal>
      <cbc:TaxAmount currencyID="DKK">279.00</cbc:TaxAmount>
      <cac:TaxSubtotal>
        <cbc:TaxableAmount currencyID="DKK">1116.00</cbc:TaxableAmount>
        <cbc:TaxAmount currencyID="DKK">279.00</cbc:TaxAmount>
        <cac:TaxCategory>
          <cbc:ID schemeAgencyID="320" schemeID="urn:oioubl:id:taxcategoryid-1.1">StandardRated</cbc:ID>
          <cbc:Percent>25.00</cbc:Percent>
          <cac:TaxScheme>
            <cbc:ID schemeAgencyID="320" schemeID="urn:oioubl:id:taxschemeid-1.1">63</cbc:ID>
            <cbc:Name>Moms</cbc:Name>
          </cac:TaxScheme>
        </cac:TaxCategory>
      </cac:TaxSubtotal>
    </cac:TaxTotal>
    <cac:Item>
      <cbc:Description>Cigars</cbc:Description>
      <cbc:Name>EXPENSES INCURRED</cbc:Name>
      <cac:SellersItemIdentification>
        <cbc:ID schemeAgencyID="9" schemeID="foo"></cbc:ID>
        <cbc:ExtendedID>Timer</cbc:ExtendedID>
      </cac:SellersItemIdentification>
      <cac:StandardItemIdentification>
        <cbc:ID schemeAgencyID="9" schemeID="foo">2014-03-06</cbc:ID>
      </cac:StandardItemIdentification>
      <cac:AdditionalItemIdentification>
        <cbc:ID schemeAgencyID="9" schemeID="foo">20000</cbc:ID>
      </cac:AdditionalItemIdentification>
    </cac:Item>
    <cac:Price>
      <cbc:PriceAmount currencyID="DKK">1116.0000</cbc:PriceAmount>
      <cbc:BaseQuantity unitCode="EA">1</cbc:BaseQuantity>
    </cac:Price>
  </cac:InvoiceLine>

  </Invoice>

We start our XSL style sheet off by doing a simple for-each loop which groups invoice lines by cac:InvoiceLine/cac:Item/cac:AdditionalItemIdentification (the consultancy lines are labelled 10000 and the expenses lines are labelled 20000); we then need to ensure that each consultancy line is grouped first by job description (cac:InvoiceLine/cac:Item/cbc:Description). We've tried several iterations of further for-each loops, for-each-group'ing and solutions which used key. However, they all seem to fall back on XPath issues

<xsl:choose>
<xsl:when test="$layouttype = '1'">

    <xsl:for-each select="cac:InvoiceLine/cac:Item/cac:AdditionalItemIdentification[cbc:ID='10000']">
        <!-- Invoice headline - Printed once-->
        <xsl:if test="number(position()) = 1">
            <fo:table-row margin-left="0mm" margin-right="0mm" white-space="normal">
                <!-- see if period is empty so colspan should be = 2 -->
                <xsl:choose>
                    <xsl:when test="../cac:StandardItemIdentification/cbc:ID != 'n/a'">
                        <fo:table-cell display-align="after">
                            <fo:block margin-bottom="1mm" font-weight="bold">
                                <xsl:value-of select="../cbc:Name"/>
                            </fo:block>
                        </fo:table-cell>
                    </xsl:when>
                    <xsl:otherwise>
                        <fo:table-cell number-columns-spanned="2" display-align="after">
                            <fo:block margin-bottom="1mm" font-weight="bold">
                                <xsl:value-of select="../cbc:Name"/>
                            </fo:block>
                        </fo:table-cell>
                    </xsl:otherwise>
                </xsl:choose>
            </fo:table-row>
        </xsl:if>

        <!-- the actual invoice line -->
        <!-- and this is where it gets tricky... -->

        <xsl:for-each-group select="../cbc:Description" group-by="../cbc:Description">
            <fo:table-row>
                <fo:table-cell>
                    <fo:block><xsl:value-of select="../cbc:Description"/></fo:block>
                </fo:table-cell>
            </fo:table-row>


            <xsl:for-each select="current-group()">

                <fo:table-row>
                    <fo:table-cell>

                        <fo:block>
                            <xsl:value-of select="../cac:SellersItemIdentification/cbc:ID"/>
                        </fo:block>

                    </fo:table-cell>
                </fo:table-row>

            </xsl:for-each>

        </xsl:for-each-group>

    </xsl:for-each>

    <xsl:for-each select="cac:InvoiceLine/cac:Item/cac:AdditionalItemIdentification[cbc:ID='20000']">

<!-- then the same pattern is repeated -->
Was it helpful?

Solution

It's probably best to avoid repeated code where you can. It is not clear if you are only ever going to have "Consultancy" and "Expenses Incurred", but it may be safe to assume not. I am also going to assume you can have multiple Item elements per InvoiceLine (although the solution given still work if this is not the case).

Anyway, you would start off by grouping Item elements by their AdditionalItemIdentification. This would create your groups for "Consultancy" and "Expenses Incurred"

<xsl:for-each-group select="cac:InvoiceLine/cac:Item" 
                    group-by="cac:AdditionalItemIdentification/cbc:ID">

Then, the title of each group would done by simply outputting the name

<xsl:value-of select="cbc:Name" />

Now, within the current group (of Item) elements, you then need to group by description

<xsl:for-each-group select="current-group()" 
                    group-by="cbc:Description">

Finally, in this group (still of Item) you want to group by SellersItemIdentification, although you probably need an extra check because not all Item elements have them (in the case of "Expenses Incurred"

<xsl:for-each-group select="current-group()[cac:SellersItemIdentification/cbc:ID != '']" 
                    group-by="cac:SellersItemIdentification/cbc:ID">

Outputing the current "seller" would them be a case of using the grouping-key, and other fields:

<xsl:value-of select="current-grouping-key()" />
<xsl:value-of select="cac:StandardItemIdentification/cbc:ID" />

As I don't know XSL-FO very well, the XSLT provided as an example will just output vague "table" and "row" elements, but it should give you the general idea.

Try this XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" 
                xmlns:cac="cac" xmlns:cbc="cbc" exclude-result-prefixes="cac cbc">
    <xsl:output indent="yes"/>
    <xsl:template match="Invoice">
        <xsl:for-each-group select="cac:InvoiceLine/cac:Item" group-by="cac:AdditionalItemIdentification/cbc:ID">
            <table>
                <row><xsl:value-of select="cbc:Name" /></row>
                <xsl:for-each-group select="current-group()" group-by="cbc:Description">
                    <row><xsl:value-of select="cbc:Description" /></row>

                    <xsl:for-each-group select="current-group()[cac:SellersItemIdentification/cbc:ID != '']" group-by="cac:SellersItemIdentification/cbc:ID">
                        <row>
                            <xsl:value-of select="current-grouping-key()" /> - <xsl:value-of select="cac:StandardItemIdentification/cbc:ID" />
                        </row>
                    </xsl:for-each-group>                    
                </xsl:for-each-group>
            </table>
        </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

This should output the following (based on your provided sample, which only includes Groucho, and not poor old Zeppo or Harpo)

<table>
   <row>CONSULTANCY</row>
   <row>Snide Remarks</row>
   <row>Groucho - 2014-02-24</row>
</table>
<table>
   <row>EXPENSES INCURRED</row>
   <row>Cigars</row>
</table>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top