XSLT: Sorting based on sum of values from other no

2019-02-25 10:03发布

问题:

I'm rather new to coding xslt and have got rather stuck trying to do the following.

I have an xml file that has breeding info for horses broken into two main sections. 1. Horses node has the performance details of individual horses as well as an id to who their sire was. 2. Sires node is list of the Sires also holding breeding specific statistics.

I need to sort the list of sires based on the sum of the 'stake' money won by their foals (i.e. in the horses node).

So a cut down xml file looks like this:

<Horses>
    <Horse>
        <ID>1</ID>
        <Name>hrsA</Name>
        <SireID>101</SireID>
        <Pace>
            <Stakes>4800</Stakes>
        </Pace>
    </Horse>
    <Horse>
        <ID>2</ID>
        <Name>hrsB</Name>
        <SireID>102</SireID>
        <Pace>
            <Stakes>3600</Stakes>
        </Pace>
    </Horse>
    <Horse>
        <ID>3</ID>
        <Name>hrsC</Name>
        <SireID>102</SireID>
        <Pace>
            <Stakes>2800</Stakes>
        </Pace>
    </Horse>
    <Horse>
        <ID>4</ID>
        <Name>hrsD</Name>
        <SireID>101</SireID>
        <Pace>
            <Stakes>56</Stakes>
        </Pace>
    </Horse>
    <Horse>
        <ID>5</ID>
        <Name>hrsE</Name>
        <SireID>100</SireID>
        <Pace>
            <Stakes>20000</Stakes>
        </Pace>
    </Horse>
    <Horse>
        <ID>6</ID>
        <Name>hrsF</Name>
        <SireID>101</SireID>
        <Trot>
            <Stakes>20000</Stakes>
        </Trot>
    </Horse>
    <Horse>
        <ID>7</ID>
        <Name>hrsG</Name>
        <SireID>101</SireID>
        <Trot>
            <Stakes>559</Stakes>
        </Trot>
    </Horse>
    <Horse>
        <ID>8</ID>
        <Name>hrsH</Name>
        <SireID>102</SireID>
        <Pace>
            <Stakes>386</Stakes>
        </Pace>
        <Trot>
            <Stakes>10000</Stakes>
        </Trot>
    </Horse>
</Horses>
<Sires>
    <Sire>
        <ID>100</ID>
        <Name>srA</Name>
        <LiveFoalsALL>117</LiveFoalsALL>
    </Sire>
    <Sire>
        <ID>101</ID>
        <Name>srB</Name>
        <LiveFoalsALL>774</LiveFoalsALL>
    </Sire>
    <Sire>
        <ID>102</ID>
        <Name>srC</Name>
        <LiveFoalsALL>43</LiveFoalsALL>
    </Sire>
</Sires>

So summing the various stakes the output would have this ordering:

Sire 101 (srB) Stakes: $25415
Sire 100 (srA) Stakes: $20000
Sire 103 (srC) Stakes: $16768.

When I was just summing and ordering the individual horse stakes for other web pages I was able to use:

<xsl:apply-templates select="Horse">
    <xsl:sort select="sum(descendant::Stakes)" data-type="number"
        order="descending"/>
</xsl:apply-templates>

I just can't figure out how to do the same for the sires referencing the horse node for summing to get the right ordering... possibly something like this where I try to say the Sire/ID is equal to the Horse/SireID:

<xsl:apply-templates select="Sire">
    <xsl:sort select="sum(//Horses/Horse[@SireID=ID]/descendant::Stakes)" 
        data-type="number" order="descending"/>
</xsl:apply-templates>

But that doesn't work, the debugger jumps straight out of the current template when it hits the sort line so my syntax must be invalid. I've been trying variations on this theme without success.

Can anyone please give me a pointer of how to call my Sire template and get the correct ordering?

Thanks,

Bryce Stenberg

回答1:

This transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:key name="kOffspring" match="Horse" use="SireID"/>

 <xsl:template match="/*">
  <xsl:apply-templates select="Sires/Sire">
   <xsl:sort select="sum(key('kOffspring', ID)/*/Stakes)"
             data-type="number" order="descending"/>
  </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="Sire">
     Sire <xsl:value-of select="concat(ID,' (', Name, ') Stakes: ')"/>
   <xsl:value-of select="sum(key('kOffspring', ID)/*/Stakes)"/>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

when applied on the provided XML document:

<t>
    <Horses>
        <Horse>
            <ID>1</ID>
            <Name>hrsA</Name>
            <SireID>101</SireID>
            <Pace>
                <Stakes>4800</Stakes>
            </Pace>
        </Horse>
        <Horse>
            <ID>2</ID>
            <Name>hrsB</Name>
            <SireID>102</SireID>
            <Pace>
                <Stakes>3600</Stakes>
            </Pace>
        </Horse>
        <Horse>
            <ID>3</ID>
            <Name>hrsC</Name>
            <SireID>102</SireID>
            <Pace>
                <Stakes>2800</Stakes>
            </Pace>
        </Horse>
        <Horse>
            <ID>4</ID>
            <Name>hrsD</Name>
            <SireID>101</SireID>
            <Pace>
                <Stakes>56</Stakes>
            </Pace>
        </Horse>
        <Horse>
            <ID>5</ID>
            <Name>hrsE</Name>
            <SireID>100</SireID>
            <Pace>
                <Stakes>20000</Stakes>
            </Pace>
        </Horse>
        <Horse>
            <ID>6</ID>
            <Name>hrsF</Name>
            <SireID>101</SireID>
            <Trot>
                <Stakes>20000</Stakes>
            </Trot>
        </Horse>
        <Horse>
            <ID>7</ID>
            <Name>hrsG</Name>
            <SireID>101</SireID>
            <Trot>
                <Stakes>559</Stakes>
            </Trot>
        </Horse>
        <Horse>
            <ID>8</ID>
            <Name>hrsH</Name>
            <SireID>102</SireID>
            <Pace>
                <Stakes>386</Stakes>
            </Pace>
            <Trot>
                <Stakes>10000</Stakes>
            </Trot>
        </Horse>
    </Horses>
    <Sires>
        <Sire>
            <ID>100</ID>
            <Name>srA</Name>
            <LiveFoalsALL>117</LiveFoalsALL>
        </Sire>
        <Sire>
            <ID>101</ID>
            <Name>srB</Name>
            <LiveFoalsALL>774</LiveFoalsALL>
        </Sire>
        <Sire>
            <ID>102</ID>
            <Name>srC</Name>
            <LiveFoalsALL>43</LiveFoalsALL>
        </Sire>
    </Sires>
</t>

produces the wanted, correct result:

 Sire 101 (srB) Stakes: 25415
 Sire 100 (srA) Stakes: 20000
 Sire 102 (srC) Stakes: 16786

Explanation:

  1. We define a key that specifies a Horse as a function of its SireID. This is useful in selecting all offspring of a given Sire just by providing its ID in a call to the standard XSLT key() function -- like this: key('kOffspring', ID).

  2. Similarly, the sum of Stakes of all offspring of a given Sire is: sum(key('kOffspring', ID)/*/Stakes) .

  3. We apply templates to all Sire elements in the XML document and sort these by the decreasing values of the sums of the Stakes of their offsprings.

  4. For each Sire we output its ID, Name and the sum of Stakes of its offspring.



回答2:

You need to use current():

<xsl:apply-templates select="Sire">
    <xsl:sort select="sum(//Horses/Horse[SireID = current()/ID]//Stakes)" 
        data-type="number" order="descending"/>

current() gives you the context node as it is outside of the current XPath expression; in this case, the current <Sire> element selected by xsl:apply-templates. Inside the predicate, the context node is the <Horse> element that's being tested against the predicate expression.

Notes:

  • You had a @ in there that you don't want; @ID refers to an ID attribute.
  • I replaced descendant::/ with //
  • As @Dimitre noted, your stylesheet will scale better to large input if you use keys instead of //foo[barID = current()/ID].