Go Premium for a chance to win a PS4. Enter to Win

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 228
  • Last Modified:

Keys

Im trying to understand the nodesets that are evaluated by 2 keys from a tutorial listed here.

http://www.codeproject.com/soap/groupxml.asp

ive created a duplicate of the tutorial and im trying to write in the nodeset which is returned by this statement

<xsl:for-each select="$lstEmployee[generate-id(.) = generate-id(key('keyEmployeeID', EmployeeID)[1])]">

im using a step by step aproach to list the xml that is held in each key (marked in red)

http://homepages.nildram.co.uk/~natkins/key/Using-Keys.htm

can someone check that firstly ive selected the correct nodes and secondly list the xml that is held in the above for-each stament.

Thanks for your help

MetalMickey
0
metalmickey
Asked:
metalmickey
  • 6
  • 2
1 Solution
 
rdcproCommented:
To me, this seems much simpler:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <!-- Define keys used to group elements -->
      <xsl:key name="keyTeamID" match="Employee" use="TeamID"/>
      <xsl:key name="keyEmployeeID" match="Employee" use="EmployeeID"/>
      <xsl:template match="Employees">
            <html>
                  <head>
                        <title>Employee Hours By Team</title>
                        <link type="text/css" rel="stylesheet" href="groupxml.css"/>
                  </head>
                  <body>
                        <h3>Employee Hours By Team</h3>
                        <table>
                              <!-- Process each Team -->
                              <xsl:for-each select="Employee[generate-id(.) = generate-id(key('keyTeamID', TeamID)[1])]">
                                    <!-- Convenience variable to hold the employees for this team.  -->
                                    <xsl:variable name="vTeamEmployees" select="/Employees/Employee[TeamName=current()/TeamName]"/>
                                    <tr>
                                          <td colspan="4" class="DarkBack">TEAM: <xsl:value-of select="TeamName"/>
                                          </td>
                                          <td colspan="1" class="DarkBack RightJustified">HOURS</td>
                                    </tr>
                                    <!-- Show details for Employees in Team -->
                                    <xsl:for-each select="$vTeamEmployees[generate-id(.) = generate-id(key('keyEmployeeID', EmployeeID)[1])]">
                                          <tr>
                                                <td colspan="4">
                                                      <xsl:value-of select="Name"/>
                                                      <xsl:value-of select="Surname"/>
                                                </td>
                                                <td colspan="1" class="RightJustified">
                                                      <!-- Show the total hours for the current Employee -->
                                                      <xsl:value-of select="sum($vTeamEmployees[EmployeeID=current()/EmployeeID]/Hours)"/>
                                                </td>
                                          </tr>
                                    </xsl:for-each>
                                    <tr>
                                          <td colspan="4" class="LightBack RightJustified">Sub-Total</td>
                                          <td colspan="1" class="LightBack RightJustified">
                                                <!-- Show the total hours for all Employees in the Team -->
                                                <xsl:value-of select="sum($vTeamEmployees/Hours)"/>
                                          </td>
                                    </tr>
                              </xsl:for-each>
                              <tr>
                                    <td colspan="4" class="RightJustified DarkBack">Grand Total</td>
                                    <td colspan="1" class="RightJustified DarkBack">
                                          <!-- Show Grand Total of hours for all Employees -->
                                          <xsl:value-of select="sum(Employee/Hours)"/>
                                    </td>
                              </tr>
                        </table>
                  </body>
            </html>
      </xsl:template>
</xsl:stylesheet>


I generally use variables only in cases where I can re-use the nodelist.  In this case, I use the same nodelist three times:

/Employees/Employee[TeamName=current()/TeamName]

So I set a variable to that, and when I need hours, I just use:

<xsl:value-of select="sum($vTeamEmployees/Hours)"/>

and if I need to filter the nodelist by employee ID, I just use:

<xsl:value-of select="sum($vTeamEmployees[EmployeeID=current()/EmployeeID]/Hours)"/>

And to iterate over the nodelist, grouping by EmployeeID, I just use:

<xsl:for-each select="$vTeamEmployees[generate-id(.) = generate-id(key('keyEmployeeID', EmployeeID)[1])]">



For me, using multiple keys is simpler if you just use each key to create an iterator (whether it's a for-each or an apply-templates) that's used to create the page structure (a table, in this case).  Then for the data, you simply use the values in the current() node to filter the entire document's nodes.  I would avoid all that call-templates stuff.

Regards,
Mike Sharp
0
 
rdcproCommented:
In fact, his original example won't work if it's possible for an employee to be a member of more than one team.  Let's say you have an administrative assistant that spends half his work week in Finance, and half in Sales.  Or more realistically, instead of Teams, let's say you're working on Projects.  It's very likely some employees will work on more than one project!

To fix this, you must add the TeamID to the keyEmployeeID.  I'll post an updated XSLT below, but I wanted to add another trick to this example that you can use, namely that you can also use keys themselves as a very simple way to aggregate the data, since they are essentially just document indexes.  The XSLT below fixes the one employee in multiple teams bug, and illustrates how you can use a third key to aggregate data. To test, I added John Smith to both teams:


<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <!-- Define keys used to group elements -->
      <xsl:key name="keyTeamID" match="Employee" use="TeamID"/>
      <xsl:key name="keyEmployeeID" match="Employee" use="concat(TeamID,EmployeeID)"/>
      <xsl:key name="keyHours" match="Hours" use="concat(../TeamID, ../EmployeeID)"/>
      <xsl:template match="Employees">
            <html>
                  <head>
                        <title>Employee Hours By Team</title>
                        <link type="text/css" rel="stylesheet" href="groupxml.css"/>
                  </head>
                  <body>
                        <h3>Employee Hours By Team</h3>
                        <table>
                              <!-- Process each Team -->
                              <xsl:for-each select="Employee[generate-id(.) = generate-id(key('keyTeamID', TeamID)[1])]">
                                    <!-- Convenience variable to hold the employees for this team.  -->
                                    <xsl:variable name="vTeamEmployees" select="/Employees/Employee[TeamID=current()/TeamID]"/>
                                    <tr>
                                          <td colspan="4" class="DarkBack">TEAM: <xsl:value-of select="TeamName"/> (<xsl:value-of select="count($vTeamEmployees)"/>)
                                          </td>
                                          <td colspan="1" class="DarkBack RightJustified">HOURS</td>
                                    </tr>
                                    <!-- Show details for Employees in Team -->
                                    <xsl:for-each select="$vTeamEmployees[generate-id(.) = generate-id(key('keyEmployeeID', concat(TeamID,EmployeeID))[1])]">
                                          <tr>
                                                <td colspan="4">
                                                      <xsl:value-of select="Name"/>
                                                      <xsl:value-of select="Surname"/>
                                                </td>
                                                <td colspan="1" class="RightJustified">
                                                      <!-- Show the total hours for the current Employee using a key to aggregate-->
                                                      <xsl:value-of select="sum(key('keyHours', concat(TeamID, EmployeeID)))"/>
                                                </td>
                                          </tr>
                                    </xsl:for-each>
                                    <tr>
                                          <td colspan="4" class="LightBack RightJustified">Sub-Total</td>
                                          <td colspan="1" class="LightBack RightJustified">
                                                <!-- Show the total hours for all Employees in the Team -->
                                                <xsl:value-of select="sum($vTeamEmployees/Hours)"/>
                                          </td>
                                    </tr>
                              </xsl:for-each>
                              <tr>
                                    <td colspan="4" class="RightJustified DarkBack">Grand Total</td>
                                    <td colspan="1" class="RightJustified DarkBack">
                                          <!-- Show Grand Total of hours for all Employees -->
                                          <xsl:value-of select="sum(Employee/Hours)"/>
                                    </td>
                              </tr>
                        </table>
                  </body>
            </html>
      </xsl:template>
</xsl:stylesheet>



By the way, using this same approach I was able to create a spectacularly complex report using data in Microsoft's Premier Support Services database, grouping not only by employee and support ticket, but by product line, and then across the table showing columns for each month going back one year at a time.  Each row displayed statistics aggregated in a large number of ways.   The XSLT was huge, but there was very little duplicate code, and best of all, it kept each "piece" of the puzzle in it's own little sandbox, which made it far easier to maintain.

The "key" to it (sorry about the pun) is to think of the xsl:key as a way of defining the page structure, then use that nodeset to filter the document nodes to include or summarize data.  

Regards,
Mike Sharp
0
 
metalmickeyAuthor Commented:
Thanks Mike,

I get the principle of storing nodesets, it's the visualising them thats my problem. I've got a project that involves an xml record that isnt too dis-similar to the employees example

What nodeset does this key hold ?

<xsl:key name="keyEmployeeID" match="Employee" use="concat(TeamID,EmployeeID)"/>

Is there a simple way of outputting the nodesets of different keys so i can see whats in them?

Thanks

MM
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
rdcproCommented:
I think I see your difficulty.  This nodeset:

<xsl:key name="keyEmployeeID" match="Employee" use="concat(TeamID,EmployeeID)"/>

has *every* Employee node in the document!  This nodeset:

<xsl:key name="keyEmployeeID" match="Employee" use="concat(Surname,FirstName)"/>

would have the exact same nodes.  It's just a way of indexing them by a key value, rather than by it's relative position.  The first one indexes the nodes by the combination of TeamID and EmployeeID, the second one indexes the exact same nodes by Surname and FirstName.  

So If I want a particular Employee node, instead of using an XPath expression relative to some other node, I can use a key value.  It's exactly like a dictionary object, or an associative array.  Think of the nodeset like:

Key                                          Value

"SharpMike"                               "//Employee[Surname = 'Sharp' and FirstName = 'Mike' "
"MickeyMetal"                             "//Employee[Surname = 'Mickey' and FirstName = 'Metal' "
etc
0
 
rdcproCommented:
Drat.  accidentally clicked "Submit"....Anyway:

The reason we use this for grouping doesn't have anything to do with the key, but with the XPath expression we use to select the nodes in a for-each or apply-templates.  Keys make it easier to do the grouping, but only because it's a convenient way to identify certain nodes.  In other words, this would have the same effect, but wouldn't execute as fast:


<xsl:for-each select="Employee[generate-id() = generate-id(//Employee[TeamID = current()/TeamID and EmployeeID = current()/EmployeeID])]">

What makes it work is the generate-id() works on only the first node in the nodeset defined by:

(//Employee[TeamID = current()/TeamID and EmployeeID = current()/EmployeeID])

So the current node is the same one as the first occurance of Employee with the same TeamID and EmployeeID only if they have the same value returned by generate-id().

Using a grouping without a key is very slow, because the processor has to recursively process every single Employee node in the document for each and every node in the node set.  If there are 10 Employee nodes, it will process 10 Employee nodes for the first one, 10 for the second, 10 for the third...etc.  Using a key allows us to process all the Employee nodes *once* and thereafter look them up by an index key, rather than an XPath expression.

Regards,
Mike Sharp
0
 
rdcproCommented:
I'd like to qualify my statement a bit, because as I re-read it, I see it doesn't quite say what I want.  This key:

<xsl:key name="keyEmployeeID" match="Employee" use="concat(TeamID,EmployeeID)"/>

has *every* Employee node in the document, indexed by BOTH TeamID and EmployeeID, and this key:

<xsl:key name="keyEmployeeID" match="Employee" use="EmployeeID"/>

has the same nodes, but indexed only by EmployeeID.  So consider this XML:

<Employees>
      <Employee>
            <TeamID>1</TeamID>
            <TeamName>Sales</TeamName>
            <TaskID>1</TaskID>
            <Hours>5</Hours>
            <EmployeeID>1</EmployeeID>
            <Name>Bob</Name>
            <Surname>Shibob</Surname>
      </Employee>
      <Employee>
            <TeamID>1</TeamID>
            <TeamName>Sales</TeamName>
            <TaskID>2</TaskID>
            <Hours>4</Hours>
            <EmployeeID>1</EmployeeID>
            <Name>Bob</Name>
            <Surname>Shibob</Surname>
      </Employee>
      <Employee>
            <TeamID>2</TeamID>
            <TeamName>Finance</TeamName>
            <TaskID>5</TaskID>
            <Hours>4</Hours>
            <EmployeeID>1</EmployeeID>
            <Name>Bob</Name>
            <Surname>Shibob</Surname>
      </Employee>
</Employees>

Bob Shibob is in two different Teams.  So if I use only the second key, and I'm grouping, I can only get the first occurance of Bob, where he's in Sales.  In order to get the other nodes, I could use these XPath expressions:

Where EmployeeID = 1:

key('keyName', EmployeeID)   <-- This is a nodeset of Employee nodes where EmployeeID = 1
key('keyName', EmployeeID)[1]   <-- This is a nodeset of only the *first* Employee node where EmployeeID = 1
key('keyName', EmployeeID)[3]   <-- This is a nodeset of only the *third* Employee node where EmployeeID = 1
key('keyName', EmployeeID)[TeamID = 1]   <-- This is a nodeset of all Employee nodes where EmployeeID =1 and TeamID = 1

So, while the <xsl:key> indexes ALL the Employee nodes, I can get access to any particular one.  That's where the Muenchian grouping comes in.   Later when we group, we're looking for the first occurance of an Employee node with a given EmployeeID.  BUT, if the employee appears in more than one Team, we'll never see the nodes for the other team.  That's why I had to add TeamID to the key; I had to be able to get unique occurrances of any node by EmployeeID AND by TeamID.  Modify my example stylesheet, removing the TeamID from the keys, and see what happens.  

This XML:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="transform-broken.xslt"?>
<Employees>
      <Employee>
            <TeamID>1</TeamID>
            <TeamName>Sales</TeamName>
            <TaskID>1</TaskID>
            <Hours>5</Hours>
            <EmployeeID>1</EmployeeID>
            <Name>Bob</Name>
            <Surname>Shibob</Surname>
      </Employee>
      <Employee>
            <TeamID>1</TeamID>
            <TeamName>Sales</TeamName>
            <TaskID>2</TaskID>
            <Hours>4</Hours>
            <EmployeeID>1</EmployeeID>
            <Name>Bob</Name>
            <Surname>Shibob</Surname>
      </Employee>
      <Employee>
            <TeamID>1</TeamID>
            <TeamName>Sales</TeamName>
            <TaskID>4</TaskID>
            <Hours>7</Hours>
            <EmployeeID>2</EmployeeID>
            <Name>Sara</Name>
            <Surname>Lee</Surname>
      </Employee>
      <Employee>
            <TeamID>2</TeamID>
            <TeamName>Finance</TeamName>
            <TaskID>5</TaskID>
            <Hours>2</Hours>
            <EmployeeID>3</EmployeeID>
            <Name>John</Name>
            <Surname>Smith</Surname>
      </Employee>
      <Employee>
            <TeamID>2</TeamID>
            <TeamName>Finance</TeamName>
            <TaskID>3</TaskID>
            <Hours>4</Hours>
            <EmployeeID>4</EmployeeID>
            <Name>Penny</Name>
            <Surname>Wise</Surname>
      </Employee>
      <Employee>
            <TeamID>2</TeamID>
            <TeamName>Finance</TeamName>
            <TaskID>5</TaskID>
            <Hours>3</Hours>
            <EmployeeID>4</EmployeeID>
            <Name>Penny</Name>
            <Surname>Wise</Surname>
      </Employee>
      <Employee>
            <TeamID>2</TeamID>
            <TeamName>Finance</TeamName>
            <TaskID>5</TaskID>
            <Hours>4</Hours>
            <EmployeeID>1</EmployeeID>
            <Name>Bob</Name>
            <Surname>Shibob</Surname>
      </Employee>
</Employees>


This XSLT:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml"/>
      <!-- Define keys used to group elements -->
      <xsl:key name="keyTeamID" match="Employee" use="TeamID"/>
      <xsl:key name="keyEmployeeID" match="Employee" use="EmployeeID"/>
      <xsl:template match="Employees">
            <table>
                  <!-- Process each Team -->
                  <xsl:for-each select="Employee[generate-id(.) = generate-id(key('keyTeamID', TeamID)[1])]">
                        <!-- Convenience variable to hold the employees for this team.  -->
                        <xsl:variable name="vTeamEmployees" select="/Employees/Employee[TeamName=current()/TeamName]"/>
                        <tr>
                              <td>TEAM: <xsl:value-of select="TeamName"/>
                              </td>
                              <td>HOURS</td>
                        </tr>
                        <!-- Show details for Employees in Team -->
                        <xsl:for-each select="$vTeamEmployees[generate-id(.) = generate-id(key('keyEmployeeID', EmployeeID)[1])]">
                              <tr>
                                    <td>
                                          <xsl:value-of select="Name"/>
                                          <xsl:value-of select="Surname"/>
                                    </td>
                                    <td>
                                          <!-- Show the total hours for the current Employee -->
                                          <xsl:value-of select="sum($vTeamEmployees[EmployeeID=current()/EmployeeID]/Hours)"/>
                                    </td>
                              </tr>
                        </xsl:for-each>
                        <tr>
                              <td>Sub-Total</td>
                              <td>
                                    <!-- Show the total hours for all Employees in the Team -->
                                    <xsl:value-of select="sum($vTeamEmployees/Hours)"/>
                              </td>
                        </tr>
                  </xsl:for-each>
                  <tr>
                        <td>Grand Total</td>
                        <td>
                              <!-- Show Grand Total of hours for all Employees -->
                              <xsl:value-of select="sum(Employee/Hours)"/>
                        </td>
                  </tr>
            </table>
      </xsl:template>
</xsl:stylesheet>


Produces this output:

<?xml version="1.0" encoding="UTF-8"?>
<table>
      <tr>
            <td>TEAM: Sales</td>
            <td>HOURS</td>
      </tr>
      <tr>
            <td>BobShibob</td>
            <td>9</td>
      </tr>
      <tr>
            <td>SaraLee</td>
            <td>7</td>
      </tr>
      <tr>
            <td>Sub-Total</td>
            <td>16</td>
      </tr>
      <tr>
            <td>TEAM: Finance</td>
            <td>HOURS</td>
      </tr>
      <tr>
            <td>JohnSmith</td>
            <td>2</td>
      </tr>
      <tr>
            <td>PennyWise</td>
            <td>7</td>
      </tr>
      <tr>
            <td>Sub-Total</td>
            <td>13</td>
      </tr>
      <tr>
            <td>Grand Total</td>
            <td>29</td>
      </tr>
</table>


Notice that Bob doesn't show up at all in the second team!


Regards,
Mike Sharp
0
 
metalmickeyAuthor Commented:
Thanks Mike, thats a great example! I think I should do some more reading, but its made the concept alot clearer.

I think you should add this to your site as the xml isnt too dificult to comprehend and the explanation introduces the use of indexing the nodes.

What i think i'll do next is use some of the keys above against the xml and use a copy-of select to see how the output nodesets differ.

Thanks again

MetalMickey
0
 
rdcproCommented:
Hmm...I think I will do that...though it won't be for a couple weeks--too darn busy at the moment.

Regards,
Mike Sharp
0

Featured Post

Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

  • 6
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now