Home > front end >  XSL Group by Country and Weight
XSL Group by Country and Weight

Time:04-02

I'm making a pricing chart from an XML that contains many rates (25k ). Each rate row specifies

  • A service name in @service
  • A country code (iso-3166) in @iso
  • A weight in @kg
  • The price in @pc

For each unique service I want to list

  • Every possible weight in column headings
  • List 1 row for every unique country
  • The 1st cell in the row shows the name (@iso) and then the @pc for the corresponding @kg

The problem I'm having, is that all of the country names are not appearing. Only 2 in Standard and Priority, and none in Express service.

        <h1>Standard</h1>
        <table>
                <tr>
                        <td>FR</td>
                </tr>
                <tr>
                        <td>CA</td>
                </tr>

        </table>
        <h1>Priority</h1>
        <table>
                <tr>
                        <td>DE</td>
                </tr>
                <tr>
                        <td>GB</td>
                </tr>
        </table>
        <h1>Express</h1>
        <table>
           NOTE--Should be 4 countries here
        </table>

I'm hoping to output something like this

 <h1>Standard</h1>
<table>
        <thead>
            <tr>
                <th>Country</th>
                <!-- One column for each unique weight -->
                <th>0.1</th>
                <th>0.15</th>
                <th>0.2</th>
                <th>0.25</th>
                <th>0.3</th>
            </tr>
        </thead>
        <tbody>
            <!-- One row for each unique country -->
            <tr>
                <td>FR</td>
                <!-- One column for each unique weight -->
                <td>202</td>
                <td>302</td>
                <td>402</td>
                <td>502</td>
                <td>602</td>
            </tr>
    
            <tr>
                <td>CA</td>
                <!-- One column for each unique weight -->
                <td>101</td>
                <td>151</td>
                <td>201</td>
                <td>251</td>
                <td>301</td>
            </tr>
    
            <!-- An so on for every unique country -->
        </tbody>
    </table>

Here's my XML / XSL

<xml>
 <rate service="Standard" iso="FR" kg="0.1" pc="202.0000"/>
 <rate service="Standard" iso="CA" kg="0.1" pc="101.0000" />
 <rate service="Standard" iso="CA" kg="0.15" pc="151.0000" />
 <rate service="Standard" iso="FR" kg="0.15" pc="302.0000" />
 <rate service="Standard" iso="FR" kg="0.2" pc="402.0000" />
 <rate service="Standard" iso="CA" kg="0.2" pc="201.0000" />
 <rate service="Standard" iso="CA" kg="0.25" pc="251.0000" />
 <rate service="Standard" iso="FR" kg="0.25" pc="502.0000" />
 <rate service="Standard" iso="FR" kg="0.3" pc="602.0000" />
 <rate service="Standard" iso="CA" kg="0.3" pc="301.0000" />

 <rate service="Priority" iso="CA" kg="0.5" pc="0.6000" />
 <rate service="Priority" iso="FR" kg="0.5" pc="0.6680" />
 <rate service="Priority" iso="DE" kg="0.5" pc="0.6070" />
 <rate service="Priority" iso="GB" kg="0.5" pc="0.7800" />
 <rate service="Priority" iso="GB" kg="0.75" pc="1.1050" />
 <rate service="Priority" iso="DE" kg="0.75" pc="0.8610" />
 <rate service="Priority" iso="FR" kg="0.75" pc="0.9470" />
 <rate service="Priority" iso="CA" kg="0.75" pc="0.8500" />
 <rate service="Priority" iso="CA" kg="1" pc="1.1000" />
 <rate service="Priority" iso="FR" kg="1" pc="1.2250" />
 <rate service="Priority" iso="DE" kg="1" pc="1.1140" />
 <rate service="Priority" iso="GB" kg="1" pc="1.4300" />

 <rate service="Express" iso="FR" kg="0.1" pc="64.6400" />
 <rate service="Express" iso="CA" kg="0.1" pc="101.0000" />
 <rate service="Express" iso="DE" kg="0.1" pc="129.2800" />
 <rate service="Express" iso="GB" kg="0.1" pc="147.6380" />
 <rate service="Express" iso="GB" kg="0.15" pc="220.7260" />
 <rate service="Express" iso="DE" kg="0.15" pc="193.2800" />
 <rate service="Express" iso="CA" kg="0.15" pc="151.0000" />
 <rate service="Express" iso="FR" kg="0.15" pc="96.6400" />
 <rate service="Express" iso="FR" kg="0.2" pc="128.6400" />
 <rate service="Express" iso="CA" kg="0.2" pc="201.0000" />
 <rate service="Express" iso="DE" kg="0.2" pc="257.2800" />
 <rate service="Express" iso="GB" kg="0.2" pc="293.8140" />
 <rate service="Express" iso="GB" kg="0.25" pc="366.9020" />
 <rate service="Express" iso="DE" kg="0.25" pc="321.2800" />
 <rate service="Express" iso="CA" kg="0.25" pc="251.0000" />
 <rate service="Express" iso="FR" kg="0.25" pc="160.6400" />
 <rate service="Express" iso="FR" kg="0.3" pc="192.6400" />
 <rate service="Express" iso="CA" kg="0.3" pc="301.0000" />
 <rate service="Express" iso="DE" kg="0.3" pc="385.2800" />
 <rate service="Express" iso="GB" kg="0.3" pc="439.9900" />
</xml>
 

And my XSL so far...

<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="service" match="rate" use="@service" />
<xsl:key name="iso" match="rate" use="@iso" />

<!-- Store off all the rates -->
<xsl:variable name='ratelist' select='//rate'/>

<xsl:template match="/">
<xml>
    <!-- Get a list of unique service names -->
    <xsl:variable name='servicelist' select="$ratelist[generate-id()=generate-id(key('service', @service))]"/>
    <xsl:for-each select='$servicelist'>
        <!-- Get the rates for just this service -->
        <xsl:variable name='servicerate' select="$ratelist[@service=current()/@service]"/>

        <!-- Get a list of unique country codes -->
        <xsl:variable name='isolist' select="$servicerate[generate-id()=generate-id(key('iso', @iso))]"/>

        <h1><xsl:value-of select='@service'/></h1>
        <table>
            <!-- Output 1 row for each unique country -->
            <xsl:for-each select='$isolist'>
                <tr>
                    <td><xsl:value-of select='@iso'/></td>
                </tr>
            </xsl:for-each>
        </table>
    </xsl:for-each>
</xml>
</xsl:template>
</xsl:stylesheet>

CodePudding user response:

As I mentioned in the comments, this is not a trivial undertaking. See if you can make sense of the following stylesheet:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:key name="rate-by-service" match="rate" use="@service" />
<xsl:key name="rate-by-country" match="rate" use="concat(@service, '|', @iso)" />
<xsl:key name="rate-by-weight" match="rate" use="concat(@service, '|', @kg)" />
<xsl:key name="value" match="rate" use="concat(@service, '|', @iso, '|', @kg)" />

<xsl:template match="/xml">
    <html>
        <!-- a group for each unique service -->
        <xsl:for-each select="rate[generate-id() = generate-id(key('rate-by-service', @service)[1])]">
            <xsl:variable name="group" select="key('rate-by-service', @service)" />
            <xsl:variable name="service" select="@service" />
            <!-- unique countries in current group  -->
            <xsl:variable name="countries" select="$group[generate-id() = generate-id(key('rate-by-country', concat(@service, '|', @iso))[1])]"/>
            <!-- unique weights in current group  -->
            <xsl:variable name="weights" select="$group[generate-id() = generate-id(key('rate-by-weight', concat(@service, '|', @kg))[1])]"/>
            <!-- output  -->
            <h1>
                <xsl:value-of select='@service'/>
            </h1>
            <table border="1">
                <thead>
                    <tr>
                        <th>Country</th>
                        <!-- a column for each unique weight in current group -->
                        <xsl:for-each select="$weights">
                            <th>
                                <xsl:value-of select='@kg'/>
                            </th>
                        </xsl:for-each>
                    </tr>
                </thead>
                <tbody>
                    <!-- a row for each unique country in current group -->
                    <xsl:for-each select="$countries">
                        <xsl:variable name="iso" select="@iso" />
                        <tr>
                            <td>
                                <xsl:value-of select='@iso'/>
                            </td>
                            <!-- a cell for each unique weight in current group -->
                            <xsl:for-each select="$weights">
                                <td>
                                    <xsl:value-of select="key('value', concat($service, '|', $iso, '|', @kg))/@pc"/>
                                </td>
                            </xsl:for-each>
                        </tr>
                    </xsl:for-each>
                </tbody>
            </table>
        </xsl:for-each>
            
    </html>
</xsl:template>

</xsl:stylesheet>

when applied to your input example, this will produce:

Result

<html>
   <h1>Standard</h1>
   <table border="1">
      <thead>
         <tr>
            <th>Country</th>
            <th>0.1</th>
            <th>0.15</th>
            <th>0.2</th>
            <th>0.25</th>
            <th>0.3</th>
         </tr>
      </thead>
      <tbody>
         <tr>
            <td>FR</td>
            <td>202.0000</td>
            <td>302.0000</td>
            <td>402.0000</td>
            <td>502.0000</td>
            <td>602.0000</td>
         </tr>
         <tr>
            <td>CA</td>
            <td>101.0000</td>
            <td>151.0000</td>
            <td>201.0000</td>
            <td>251.0000</td>
            <td>301.0000</td>
         </tr>
      </tbody>
   </table>
   <h1>Priority</h1>
   <table border="1">
      <thead>
         <tr>
            <th>Country</th>
            <th>0.5</th>
            <th>0.75</th>
            <th>1</th>
         </tr>
      </thead>
      <tbody>
         <tr>
            <td>CA</td>
            <td>0.6000</td>
            <td>0.8500</td>
            <td>1.1000</td>
         </tr>
         <tr>
            <td>FR</td>
            <td>0.6680</td>
            <td>0.9470</td>
            <td>1.2250</td>
         </tr>
         <tr>
            <td>DE</td>
            <td>0.6070</td>
            <td>0.8610</td>
            <td>1.1140</td>
         </tr>
         <tr>
            <td>GB</td>
            <td>0.7800</td>
            <td>1.1050</td>
            <td>1.4300</td>
         </tr>
      </tbody>
   </table>
   <h1>Express</h1>
   <table border="1">
      <thead>
         <tr>
            <th>Country</th>
            <th>0.1</th>
            <th>0.15</th>
            <th>0.2</th>
            <th>0.25</th>
            <th>0.3</th>
         </tr>
      </thead>
      <tbody>
         <tr>
            <td>FR</td>
            <td>64.6400</td>
            <td>96.6400</td>
            <td>128.6400</td>
            <td>160.6400</td>
            <td>192.6400</td>
         </tr>
         <tr>
            <td>CA</td>
            <td>101.0000</td>
            <td>151.0000</td>
            <td>201.0000</td>
            <td>251.0000</td>
            <td>301.0000</td>
         </tr>
         <tr>
            <td>DE</td>
            <td>129.2800</td>
            <td>193.2800</td>
            <td>257.2800</td>
            <td>321.2800</td>
            <td>385.2800</td>
         </tr>
         <tr>
            <td>GB</td>
            <td>147.6380</td>
            <td>220.7260</td>
            <td>293.8140</td>
            <td>366.9020</td>
            <td>439.9900</td>
         </tr>
      </tbody>
   </table>
</html>

Rendered:

enter image description here

  • Related