Warm tip: This article is reproduced from serverfault.com, please click

xslt: sort, compare and return values

发布于 2020-12-02 20:39:22

So I'm working with an xml of hockey statistics that is generated by a separate program. In generates a node for each player, with sub nodes for each season (plus one for career total), with sub nodes in each season for the type of stats, and then values within those sub nodes.

Sample of XML:

  <player name="Player A" checkname="A,PLAYER">
      <stats year="2017-18" gp="30">
        <shots g="3" a="1" pts="4" sh="23" pct=".130" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="0" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="0" minutes="0" minor="0" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="10" minus="9" plusminus="+1" facewon="67" facelost="71" facepct=".486" blk="17"></misc>
      </stats>
      <stats year="2018-19" gp="37">
        <shots g="5" a="5" pts="10" sh="28" pct=".179" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="0" sh="1" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="2" minutes="4" minor="2" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="15" minus="13" plusminus="+2" facewon="118" facelost="106" facepct=".527" blk="23"></misc>
      </stats>
      <stats year="2019-20" gp="3">
        <shots g="2" a="0" pts="2" sh="6" pct=".333" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="1" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="0" minutes="0" minor="0" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="1" minus="2" plusminus="-1" facewon="36" facelost="30" facepct=".545" blk="3"></misc>
      </stats>
      <stats year="TOTAL" gp="70">
        <shots g="10" a="6" pts="16" sh="57" pct=".175" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="1" sh="1" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="2" minutes="4" minor="2" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="26" minus="24" plusminus="+2" facewon="221" facelost="207" facepct=".516" blk="43"></misc>
      </stats>

  </player>

  <player name="Player B" checkname="B,PLAYER">
      <stats year="2016-17" gp="37">
        <shots g="1" a="11" pts="12" sh="24" pct=".042" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="0" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="7" minutes="14" minor="7" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="33" minus="26" plusminus="+7" facewon="1" facelost="0" facepct="1.000" blk="54"></misc>
      </stats>
      <stats year="2017-18" gp="36">
        <shots g="3" a="14" pts="17" sh="47" pct=".064" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="1" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="8" minutes="16" minor="8" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="31" minus="34" plusminus="-3" facewon="0" facelost="1" facepct=".000" blk="43"></misc>
      </stats>
      <stats year="2018-19" gp="37">
        <shots g="3" a="13" pts="16" sh="44" pct=".068" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="0" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="5" minutes="10" minor="5" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="40" minus="36" plusminus="+4" facewon="1" facelost="0" facepct="1.000" blk="47"></misc>
      </stats>
      <stats year="2019-20" gp="3">
        <shots g="0" a="0" pts="0" sh="2" pct=".000" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="0" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="2" minutes="4" minor="2" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="1" minus="4" plusminus="-3" facewon="0" facelost="0" facepct=".000" blk="4"></misc>
      </stats>
      <stats year="TOTAL" gp="113">
        <shots g="7" a="38" pts="45" sh="117" pct=".060" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="1" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="22" minutes="44" minor="22" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="105" minus="100" plusminus="+5" facewon="2" facelost="1" facepct=".667" blk="148"></misc>
      </stats>

  </player>

  <player name="Player C" checkname="C,PLAYER">
      <stats year="2017-18" gp="25">
        <shots g="1" a="3" pts="4" sh="25" pct=".040" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="0" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="4" minutes="19" minor="2" major="1" misc10="0" miscgame="1" miscgross="0" match="0"></penalty>
        <misc plus="2" minus="8" plusminus="-6" facewon="1" facelost="5" facepct=".167" blk="14"></misc>
      </stats>
      <stats year="2018-19" gp="33">
        <shots g="3" a="2" pts="5" sh="47" pct=".064" ps="0" psatt="0"></shots>
        <goaltype gw="1" pp="0" sh="0" ua="1" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="7" minutes="14" minor="7" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="5" minus="11" plusminus="-6" facewon="3" facelost="5" facepct=".375" blk="25"></misc>
      </stats>
      <stats year="2019-20" gp="3">
        <shots g="0" a="1" pts="1" sh="3" pct=".000" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="0" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="1" minutes="2" minor="1" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="1" minus="3" plusminus="-2" facewon="0" facelost="3" facepct=".000" blk="4"></misc>
      </stats>
      <stats year="TOTAL" gp="61">
        <shots g="4" a="6" pts="10" sh="75" pct=".053" ps="0" psatt="0"></shots>
        <goaltype gw="1" pp="0" sh="0" ua="1" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="12" minutes="35" minor="10" major="1" misc10="0" miscgame="1" miscgross="0" match="0"></penalty>
        <misc plus="8" minus="22" plusminus="-14" facewon="4" facelost="13" facepct=".235" blk="43"></misc>
      </stats>

  </player>

  <player name="Player D" checkname="D,PLAYER">
      <stats year="2018-19" gp="29">
        <shots g="1" a="9" pts="10" sh="33" pct=".030" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="1" sh="0" ua="0" fg="1" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="2" minutes="4" minor="2" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="12" minus="12" plusminus="0" facewon="116" facelost="94" facepct=".552" blk="8"></misc>
      </stats>
      <stats year="2019-20" gp="3">
        <shots g="0" a="0" pts="0" sh="2" pct=".000" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="0" sh="0" ua="0" fg="0" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="1" minutes="2" minor="1" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="0" minus="2" plusminus="-2" facewon="22" facelost="24" facepct=".478" blk="2"></misc>
      </stats>
      <stats year="TOTAL" gp="32">
        <shots g="1" a="9" pts="10" sh="35" pct=".029" ps="0" psatt="0"></shots>
        <goaltype gw="0" pp="1" sh="0" ua="0" fg="1" ot="0" en="0" hat="0" gt="0" so="0"></goaltype>
        <penalty count="3" minutes="6" minor="3" major="0" misc10="0" miscgame="0" miscgross="0" match="0"></penalty>
        <misc plus="12" minus="14" plusminus="-2" facewon="138" facelost="118" facepct=".539" blk="10"></misc>
      </stats>

  </player>

What I'm trying to do is have an xslt file, return the value and the name of the top player(s) for a specified stat category for a given season. For instance starting off with points (@pts).

I've been able to get it to get it to return the top result and player using some basic sorting, however, I want to add in more logic in situations where there are ties for the top.

This is what I have so far for finding the top points leader:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" version="1.0" exclude-result-prefixes="msxsl">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/">
        <STATS>
            <season_leaders>
                <xsl:for-each select="player/stats[@year='2019-20']">
                    <xsl:sort select="shots/@pts"  data-type="number" order="descending"/>
                    <xsl:if test="(position() = 1)">
                        <points>
                            <xsl:value-of select="shots/@pts"/> - <xsl:value-of select="../@name"/>
                        </points>
                    </xsl:if>
                </xsl:for-each>
            </season_leaders>
        </STATS>
    </xsl:template>
</xsl:stylesheet>

Just a text return of the leading value and the leader's name, seperated by a dash, is exactly what I want. However, I would like it to compare the top three values, and in the case of a two-person tie, return the single top value and the last names of the two people tied. And in the case of a three-or-more-person tie, return the top value with text saying "multiple tied."

I've tried using the "position()" function in calling the value but it doesnt seem to work, i.e. <xsl:value-of select="shots[position()=1]/@pts"/> and different variations.

Any help in how I can get it to compare the top sorted values and return different results based on the results of that comparison? Lastly, I believe it has to be done in XSLT version 1.0 sadly.

Questioner
CycloneBeaverBadger
Viewed
0
michael.hor257k 2020-12-03 05:44:21

Try something like:

XSLT 1.0

<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:strip-space elements="*"/>

<xsl:param name="year" select="'2019-20'"/>
<xsl:key name="stats" match="stats" use="concat(@year, '|', shots/@pts)" />

<xsl:template match="/players">
    <STATS>
        <season_leaders>
            <xsl:variable name="max">
                <xsl:for-each select="player/stats[@year=$year]/shots">
                    <xsl:sort select="@pts" data-type="number" order="descending"/>
                    <xsl:if test="(position()=1)">
                        <xsl:value-of select="@pts"/>
                    </xsl:if>
                </xsl:for-each>
            </xsl:variable>
            <xsl:variable name="leaders" select="key('stats', concat($year, '|', $max))" />
            <points>
                <xsl:value-of select="$max"/>
                <xsl:text> - </xsl:text>
                <xsl:choose>
                    <xsl:when test="count($leaders) > 2">multiple tied.</xsl:when>
                    <xsl:otherwise>
                        <xsl:for-each select="$leaders">
                            <xsl:value-of select="../@name"/>
                            <xsl:if test="position()!=last()">, </xsl:if>
                        </xsl:for-each>
                    </xsl:otherwise>
                </xsl:choose>
            </points>
        </season_leaders>
    </STATS>
</xsl:template>

</xsl:stylesheet>

Demo (using a well-formed XML as the input: http://xsltfiddle.liberty-development.net/gVrvcxm