Search code examples
xsltxslt-1.0xslt-2.0

Sort inside a formating node


I need to display the XML with the total sum of the distance of the day sorted so that the day with most kilometers is shown first I was able only to make the sum of the distance

Input

<output>
    <cars>
        <car>
          <id>1</id>
          <license>B-01-TST</license>
        </car>
        <car>
          <id>2</id>
          <license>IF-02-TST</license>
        </car>
    </cars>
    <distances>
        <distance>
          <id_car>1</id_car>
          <date>20110901</date>
          <distance>111</distance>
        </distance>     
        <distance>
          <id_car>1</id_car>
          <date>20110903</date>
          <distance>56</distance>
        </distance>
        <distance>
          <id_car>2</id_car>
          <date>20110901</date>
          <distance>92</distance>
        </distance>
        <distance>
          <id_car>2</id_car>
          <date>20110902</date>
          <distance>97</distance>
        </distance>
    </distances>
</output>

Output expected

<output>
<cars>
    <car>
        <id>1</id>
        <license>B-01-TST</license>
        <distance totalKm="Day 01: 111"/>
        <distance totalKm="Day 03: 56"/>
    </car>
    <car>
        <id>2</id>
        <license>IF-02-TST</license>
        <distance totalKm="Day 01: 92"/>
        <distance totalKm="Day 02: 97"/>
    </car>
</cars>

I'm not able to copy all the XSL because its too much code for the question, so the main info about is this

So far the XSL looks like this:

<xsl:key name="byCarIdAndDate" match="distance" use="concat(id_car, '|', date)"/>
<xsl:key name="byCarId" match="distance" use="id_car"/>
<xsl:template match="car">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
            <xsl:apply-templates select="key('byCarId', id)"/>
        </xsl:copy>
    </xsl:template>
<xsl:template match="distance[generate-id()= generate-id(key('byCarIdAndDate', concat(id_car, '|', date))[1])]">
    <xsl:variable name="thisDate" select="key('byCarIdAndDate', concat(id_car, '|', date))"/>
    <xsl:variable name="sum" select="sum($thisDate/distance)"/>
    <!--make some sort by higher value -->
    <distance totalKm="Day {substring(date, 7, 2)}: {$sum}"/>
</xsl:template>

to remove the distance block I'm doing like this template match="distances| distance"


Solution

  • I think it is better to resort to xsl:for-each-group then to group by date and to allow us to easily sum a group:

    <?xml version="1.0" encoding="UTF-8" ?>
    <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    
        <xsl:output indent="yes"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:key name="byCarId" match="distances/distance" use="id_car"/>
    
        <xsl:template match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="distances"/>
    
        <xsl:template match="car">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
                <xsl:for-each-group select="key('byCarId', id)" group-by="date">
                    <xsl:sort select="sum(current-group()/distance)" order="descending"/>
                    <xsl:apply-templates select="."/>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="distance">
            <distance totalKm="Day {substring(date, 7, 2)}: {sum(current-group()/distance)}"/>
        </xsl:template>
    
    </xsl:transform>
    

    http://xsltransform.net/3MvmrAX