Subject: Creating a medal table
Posted By: awood2000 Post Date: 11/9/2004 11:13:52 AM
I would like to display the following XML as a table showing who got what medal i.e. 1st 2nd and 3rd (Gold, Silver Bronze)

The output I wishing to create would look like this:

Name     Gold  Silver  Bronze
Player1   3      1      0
Player2   1      3      0
Player3   0      0      4

<games>
    <records type="Type1">
        <event name="event1" score="time">
            <player name="Player1">
                <record-score>10.00</record-score>
            </player>
            <player name="Player2">
                <record-score>11.00</record-score>
            </player>
            <player name="Player3">
                <record-score>12.00</record-score>
            </player>
        </event>
        <event name="event2" score="time">
            <player name="Player1">
                <record-score>10.00</record-score>
            </player>
            <player name="Player2">
                <record-score>15.00</record-score>
            </player>
            <player name="Player3">
                <record-score>20.00</record-score>
            </player>
        </event>
    </records>
    <records type="Type2">
        <event name="event3" score="points">
            <player name="Player1">
                <record-score>200</record-score>
            </player>
            <player name="Player2">
                <record-score>100</record-score>
            </player>
            <player name="Player3">
                <record-score>50</record-score>
            </player>
        </event>
        <event name="event4" score="points">
            <player name="Player1">
                <record-score>10</record-score>
            </player>
            <player name="Player2">
                <record-score>20</record-score>
            </player>
            <player name="Player3">
                <record-score>5</record-score>
            </player>
        </event>
    </records>
</games>

The attribute score on the event tag is used to determine the order i.e. "time" means the quickest time (lowest record-score) wins gold and "points" means the highest points (highest record-score) wins gold.

Example: Player1 wins gold on event1 as his time of 10.00 is quicker than Player2 11.00 and Player3 12.00

My XML has more events but I've inclued a small subset above.

Hope this makes sence and something can provide a solution as I will be most grateful.

Thanks
Ally
Reply By: jkmyoung Reply Date: 11/9/2004 1:38:53 PM
I suggest you break it down into 2 parts:
1. determining the medal standings per event
2. summing up the standings.

1. probably would have to use a temporary variable.
<xsl:variable name="medals">
  <medals>
    <xsl:for-each select="records">
       ... so on til you get to event

<xsl:for-each select="player">
<xsl:sort select="record-score"><!-- key part here -->
<player>
  <xsl:copy-of select="@name"/>

  <xsl:choose>
    <xsl:when test="position()=1"><gold/></xsl:when>
    <xsl:when test="position()=2"><silver/></xsl:when>
    <xsl:when test="position()=3"><bronze/></xsl:when>
...

you need to have some sort of if or choose statement
so that you sort ascending(default) if it's time, and descending (order="descending") if it's points.

 
2. Counting should be easy: for each player:
variable name="cname" select="@name"
select="count($medals/records/event/player[@name=$cname]/gold"
select="count($medals/records/event/player[@name=$cname]/silver"
select="count($medals/records/event/player[@name=$cname]/bronze"


Reply By: awood2000 Reply Date: 11/10/2004 12:36:26 PM
Thanks for your help.
I've tried to create the stylesheet but I'm getting the error
"Expression must evaluate to a node-set" on the
xsl:value-of select="count($medals/records/event/player[@name=...
lines in the code below.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
        <xsl:variable name="medals">
            <medals>
                <xsl:for-each select="records">
                    <records>
                        <xsl:for-each select="event">
                            <event>
                                <xsl:for-each select="player">
                                    <xsl:sort select="record-score"/>
                                    <player>
                                        <!-- Does the below copy-of @name build this?
                                            <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
                                        -->
                                        <xsl:copy-of select="@name"/>
                                        <xsl:choose>
                                            <xsl:when test="position()=1">
                                                <gold/>
                                            </xsl:when>
                                            <xsl:when test="position()=2">
                                                <silver/>
                                            </xsl:when>
                                            <xsl:when test="position()=3">
                                                <bronze/>
                                            </xsl:when>
                                        </xsl:choose>
                                    </player>
                                </xsl:for-each>
                            </event>
                        </xsl:for-each>
                    </records>
                </xsl:for-each>
            </medals>
        </xsl:variable>
        <xsl:variable name="events" select="/games/records/event"/>    
    
    <xsl:template match="/">
        <html>
            <body>
                <h2>Medal Table</h2>
                <table border="1">
                    <tr>
                        <th>Name</th>
                        <th>Gold</th>
                        <th>Silver</th>
                        <th>Bronze</th>
                    </tr>
                    <tr>
                        <td>Player 1</td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player1']/gold)"/></td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player1']/silver)"/></td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player1']/bronze)"/></td>
                    </tr>
                    <tr>
                        <td>Player 2</td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player2']/gold)"/></td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player2']/silver)"/></td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player2']/bronze)"/></td>
                    </tr>
                    <tr>
                        <td>Player 3</td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player3']/gold)"/></td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player3']/silver)"/></td>
                        <td><xsl:value-of select="count($medals/records/event/player[@name='Player3']/bronze)"/></td>
                    </tr>
                </table>
                <br/>
                <xsl:text>Total number of events is </xsl:text>
                <xsl:value-of select="count($events)"/>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

Can you point me in the right direction.

I know I haven't changed the sort order yet depending on the "score" attribute value.

Thanks again
Ally


Reply By: jkmyoung Reply Date: 11/10/2004 1:23:28 PM
I forgot, this only works in XSLT 1.1 and above, (or depends on your processor) because of a change to result-tree fragments not being node-sets. If you change to version 1.1 and the processor accepts it, you're set.

If you have to use XSLT 1.0, I suggest seperating it into the 2 transformations. After the 1st you have a temporary file (what the variable would be). Then you run the 2nd XSLT transformation on the temp file. You probably could leave it as one transformation, but then the logic becomes a lot more complicated, not easily solvable.

Reply By: joefawcett Reply Date: 11/11/2004 4:35:51 AM
Which XSLT processor are you using? Most have a function, normally called ns:node-set, that returns a node-set from an RTF (result tree fragment).



--

Joe (Microsoft MVP - XML)
Reply By: awood2000 Reply Date: 11/11/2004 12:37:25 PM
quote:
Originally posted by joefawcett

Which XSLT processor are you using? Most have a function, normally called ns:node-set, that returns a node-set from an RTF (result tree fragment).
--

Joe (Microsoft MVP - XML)



I'm using IE6 so which is the minimum version of msxml do I need to install?

I've also looked at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/xmlsdk/html/XSLT_Example_Initiate.asp maybe I'm not using the latest version of msxml that's on my desktop but I need to check.

Thanks for the help
Ally

Reply By: joefawcett Reply Date: 11/12/2004 3:51:34 AM
If you're using IE with an embedded stylesheet then version 3 is the best you can hope for, it must be installed in replace mode.
It has a node-set function, see the docs.


--

Joe (Microsoft MVP - XML)
Reply By: awood2000 Reply Date: 11/12/2004 12:25:39 PM
Joe,

I've used the following html via IE to force using the required version of MSXML but it still fails with the same error for versions 3 and 4.
"Expression must evaluate to a node-set" on the
xsl:value-of select="count($medals/records/event/player[@name=....

<HTML>
<HEAD>
  <TITLE>sample</TITLE>
  <SCRIPT language = "javascript">
     function init()
     {
        var srcTree = new ActiveXObject("Msxml2.DOMDocument.4.0");
        srcTree.async=false;
        // You can substitute other XML file names here.
        srcTree.load("example.xml");

        var xsltTree= new ActiveXObject("Msxml2.DOMDocument.4.0");
        xsltTree.async = false;
        // You can substitute other XSLT file names here.
        xsltTree.load("example.xsl");

        resTree.innerHTML = srcTree.transformNode(xsltTree);
     }
  </SCRIPT>
</HEAD>

<BODY onload = "init()" >
   <div id="resTree"></div>
</BODY>
</HTML>

The example xml and xsl are from my previous posts.  Should I be changing the xsl in some way?
Maybe I should take jkmyoung's advice and use a temporary file.

Ally

Reply By: jkmyoung Reply Date: 11/12/2004 12:41:02 PM
I want to add a warning about using that method as well: if you actually write the file to disk, performance will be slow because:
1. File IO is slow.
2. You'd have to reparse the tree.

If you use a method that keeps the temporary file in memory, then you won't have that problem.

Reply By: joefawcett Reply Date: 11/13/2004 9:45:17 AM

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                              xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                              exclude-result-prefixes="msxsl">
  <xsl:output method="html"/>
  <xsl:key name="players" use="@name" match="player"/>
  <xsl:template match="/">
    <xsl:variable name="results">
      <xsl:apply-templates select="games/records/event"/>
    </xsl:variable>
    <xsl:variable name="nsResults" select="msxsl:node-set($results)"/>    
      <table>
      <thead>
        <tr><th>Name</th><th>Gold</th><th>Silver</th><th>Bronze</th></tr>      
      </thead>
      <tbody>
        <xsl:for-each select="games/records/event/player[generate-id() = generate-id(key('players', @name)[1])]">
          <xsl:sort data-type="text" order="ascending" select="@name"/>
          <tr>
            <td><xsl:value-of select="@name"/></td>
            <td align="center"><xsl:value-of select="count($nsResults/result[@player = current()/@name and @position = 1])"/></td>
            <td align="center"><xsl:value-of select="count($nsResults/result[@player = current()/@name and @position = 2])"/></td>
            <td align="center"><xsl:value-of select="count($nsResults/result[@player = current()/@name and @position = 3])"/></td>
          </tr>
        </xsl:for-each>
      </tbody>
      </table>    
  </xsl:template>
  
  <xsl:template match="event">
    <xsl:variable name="sortDirection">
      <xsl:choose>
        <xsl:when test="@score = 'time'">ascending</xsl:when>
        <xsl:when test="@score = 'points'">descending</xsl:when>
      </xsl:choose>
    </xsl:variable>
    <xsl:apply-templates select="player">
      <xsl:sort data-type="number" order="{$sortDirection}" select="record-score"/>
    </xsl:apply-templates>    
  </xsl:template>
  
  <xsl:template match="player">    
    <result position="{position()}" player="{@name}"/>    
  </xsl:template>
</xsl:stylesheet>


Beyond me to do it without using node-set function...



--

Joe (Microsoft MVP - XML)
Reply By: awood2000 Reply Date: 11/15/2004 6:02:04 AM
That's fantastic, thanks a lot guys.

The only minor things I need to resolve now are how to cater for score’s that are the same.  So if 2 players have the same time then they should get the same medal.  I couldn't see a way to use sort to resolve this so will I'll have to check each score manually?

The other issue is that I've changed the xml to have 2 score tags per player for some events but not all <record-score-a> and <record-score-b>.
Which lead me to create a record-tag variable but I was having trouble passing this between templates as it's value get's cleared.

Example below:
.
.
.
    <xsl:template match="records">
        <xsl:variable name="tag-name">
            <xsl:choose>
                <xsl:when test="@type = 'Type2'">record-score</xsl:when>
                <xsl:when test="@type = 'Type3'">record-mixed</xsl:when>
                <xsl:otherwise>recordAB</xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        <xsl:apply-templates select="events/event">
             <xsl:with-param name="record-tag">$tag-name</xsl:with-param>
             <!-- I've tried hard coding a value here but it's still cleared in the next template -->
        </xsl:apply-templates>
    </xsl:template>
    
    <xsl:template match="events/event">
        <xsl:param name="record-tag"/> <!-- I think this is clearing the value passed from the previous template -->
        <!-- To keep example short I've cut the sortDirection definition from here -->
        <xsl:choose>
             <xsl:when test="$record-tag = 'recordAB'">
                 <xsl:apply-templates select="player">
                    <xsl:sort data-type="number" order="{$sortDirection}" select="record-score-a"/>
                </xsl:apply-templates>
                <xsl:apply-templates select="player">
                    <xsl:sort data-type="number" order="{$sortDirection}" select="record-score-b"/>
                </xsl:apply-templates>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="player">
                    <xsl:sort data-type="number" order="{$sortDirection}" select="$record-tag"/>
                </xsl:apply-templates>
            </xsl:otherwise>
        </xsl:choose>
.
.
.

Any further help would be much appreciated.
Ally

Reply By: joefawcett Reply Date: 11/15/2004 6:06:55 AM
Can you show the revised XML? What criteria do you use to decide wheteher to use record-score-a or record-score-b?



--

Joe (Microsoft MVP - XML)
Reply By: awood2000 Reply Date: 11/15/2004 6:26:20 AM
Adding the 2 score tags now means there are 2 medals for each event.  So Player 1 would get 2 golds for event 1 and 2 golds for event 2.

Here is an example of the updated xml.

<games xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <records type="Type1">
        <events>
            <event name="event1" score="time">
                <player name="Player1">
                    <record-score-a>10.00</record-score-a>
                    <record-score-b>20.00</record-score-b>
                </player>
                <player name="Player2">
                    <record-score-a>11.00</record-score-a>
                    <record-score-b>22.00</record-score-b>
                </player>
                <player name="Player3">
                    <record-score-a>12.00</record-score-a>
                    <record-score-b>24.00</record-score-b>
                </player>
            </event>
            <event name="event2" score="time">
                <player name="Player1">
                    <record-score-a>10.00</record-score-a>
                    <record-score-b>20.00</record-score-b>
                </player>
                <player name="Player2">
                    <record-score-a>15.00</record-score-a>
                    <record-score-b>30.00</record-score-b>
                </player>
                <player name="Player3">
                    <record-score-a>20.00</record-score-a>
                    <record-score-b>40.00</record-score-b>
                </player>
            </event>
        </events>
    </records>
    <records type="Type2">
        <events>
            <event name="event3" score="points">
                <player name="Player1">
                    <record-score>200</record-score>
                </player>
                <player name="Player2">
                    <record-score>100</record-score>
                </player>
                <player name="Player3">
                    <record-score>50</record-score>
                </player>
            </event>
            <event name="event4" score="points">
                <player name="Player1">
                    <record-score>10</record-score>
                </player>
                <player name="Player2">
                    <record-score>20</record-score>
                </player>
                <player name="Player3">
                    <record-score>5</record-score>
                </player>
            </event>
        </events>
    </records>
</games>

Ally


Go to topic 20511

Return to index page 716
Return to index page 715
Return to index page 714
Return to index page 713
Return to index page 712
Return to index page 711
Return to index page 710
Return to index page 709
Return to index page 708
Return to index page 707