Warning: XSLTProcessor::transformToXml() [function.XSLTProcessor-transformToXml]: I/O warning : failed to load external entity "/home/webucato/courseSourceFiles/XML/ClassFiles/Keys/Demos/GenerateId.xml" in /home/webucato/public_html/xslt.inc.php on line 29

Warning: XSLTProcessor::transformToXml() [function.XSLTProcessor-transformToXml]: I/O warning : failed to load external entity "/home/webucato/courseSourceFiles/XML/ClassFiles/Keys/Demos/GenerateId.xsl" in /home/webucato/public_html/xslt.inc.php on line 29

Warning: XSLTProcessor::transformToXml() [function.XSLTProcessor-transformToXml]: I/O warning : failed to load external entity "/home/webucato/courseSourceFiles/XML/ClassFiles/Keys/Demos/GenerateIdOutput.xml" in /home/webucato/public_html/xslt.inc.php on line 29

Working with Keys

In this lesson of the XSLT tutorial, you will learn...
  1. How to create keys.
  2. To use keys for cross referencing.
  3. To work with the generate-id() function.
  4. To use keys for grouping.

Key Basics

XSLT keys provide a simple and efficient way of looking up values based on other values. First, we will take a look at the syntax of keys and then we will look at how they are used in practice.

<xsl:key/>

Keys are created with the <xsl:key> tag, which must be a child of <xsl:stylesheet/>. It has three required attributes shown in the table below.

xsl:key Attributes
Attribute Description
name Name of the key.
match Pattern to match.
use The part of the matched node that will serve as the look-up index.

The key() Function

The key() function is used to look up the node or nodes specified by the key. It takes two arguments: the name of the key and the index.

The example below shows how to create a key and then look up a value in the key.

Code Sample: Keys/Demos/SimpleKey.xml

<?xml version="1.0"?>
<?xml-stylesheet href="SimpleKey.xsl" type="text/xsl"?>
<beatles>
 <beatle link="http://www.paulmccartney.com">
  <name>
   <firstname>Paul</firstname>
   <lastname>McCartney</lastname>
  </name>
  <role>Singer</role>
 </beatle>
 <beatle link="http://www.johnlennon.com">
  <name>
   <firstname>John</firstname>
   <lastname>Lennon</lastname>
  </name>
  <role>Singer</role>
 </beatle>
 <beatle link="http://www.georgeharrison.com">
  <name>
   <firstname>George</firstname>
   <lastname>Harrison</lastname>
  </name>
  <role>Guitarist</role>
 </beatle>
 <beatle link="http://www.ringostarr.com">
  <name>
   <firstname>Ringo</firstname>
   <lastname>Starr</lastname>
  </name>
  <role>Drummer</role>
 </beatle>
</beatles>

Code Sample: Keys/Demos/SimpleKey.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes"/>
 <xsl:key name="keyBeatle" match="beatle" use="name/firstname"/>
 <xsl:param name="FirstName" select="'John'"/>
 
 <xsl:template match="/">
  <Match>
   <xsl:value-of select="key('keyBeatle',$FirstName)/@link"/>
  </Match>
 </xsl:template>
 
</xsl:stylesheet>
Code Explanation

In the example above, the key is created with this line:

<xsl:key name="keyBeatle" match="beatle" use="name/firstname"/>

Imagine that it creates a lookup table that looks something like this:

Index Node-set
Paul Paul McCartney beatle node
John John Lennon beatle node
George George beatle node
Ringo Ringo beatle node

The key() function is used to look up the node or nodes specified by the key. It takes two arguments: the name of the key and the index.

<xsl:value-of select="key('keyBeatle',$FirstName)/@link"/>

As $Firstname is set to 'John' in the xsl:param, this line above returns "http://www.johnlennon.com". As you can see, the key() function returns nodes in the XML document. We can use XPath to drill down further into these nodes. In the example above, we drill down to the link attribute of the beatle node returned by key().

Improving Performance with Keys

The major benefit of using keys is that the XSLT processor creates an index of the matched nodes, which allows it to access those nodes from anywhere without searching through the whole node tree.

Cross References

Keys are commonly used for cross referencing. To illustrate, we'll first study an example of a transformation that doesn't use keys. Take a look at the following XML document, which contains a list of U.S. states and their abbreviations followed by a list of cities with their states and populations.

Code Sample: Keys/Demos/CitiesWithoutKey.xml

<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="CitiesWithOutKey.xsl"?><CityStates> <States>  <State Abbr="AL">Alabama</State>  <State Abbr="AK">Alaska</State>  <State Abbr="AZ">Arizona</State>  <State Abbr="AR">Arkansas</State>  <State Abbr="CA">California</State>  <State Abbr="CO">Colorado</State>  <State Abbr="CT">Connecticut</State>  <State Abbr="DE">Delaware</State>  <State Abbr="FL">Florida</State>
---- Code Omitted ----
</States> <Cities Source="http://www.city-data.com/top1.html"> <City State="NY" Population="8084316">New York</City> <City State="CA" Population="3798981">Los Angeles</City> <City State="IL" Population="2886251">Chicago</City> <City State="TX" Population="2009834">Houston</City> <City State="PA" Population="1492231">Philadelphia</City> <City State="AZ" Population="1371960">Phoenix</City> <City State="CA" Population="1259532">San Diego</City> <City State="TX" Population="1211467">Dallas</City> <City State="TX" Population="1194222">San Antonio</City>
---- Code Omitted ----
</Cities></CityStates>

Our goal is to take this document and output an HTML page that looks like this:

To do this, we'll loop through the City elements outputting a list item for each one. But the City elements just contain the state abbreviation and we want to output the full state name. To do this, we'll need to look up the state name in the State elements. The following code sample shows how to do this without keys.

Code Sample: Keys/Demos/CitiesWithoutKey.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes"/>
 <xsl:template match="/">
  <html>
   <head>
    <title>Largest Cities in the United States</title>
   </head>
   <body>
   <h1>Largest Cities in the United States</h1>
   <ol>
    <xsl:for-each select="//City">
     <xsl:variable name="State" select="@State"/>
     <li>
      <xsl:value-of select="."/>
      <xsl:text> is in the state of </xsl:text>
      <xsl:value-of select="//States/State[@Abbr=$State]"/>
      <xsl:text> and has a population of </xsl:text>
      <xsl:value-of select="format-number(@Population,'#,###')"/>
      <xsl:text>.</xsl:text>
     </li>
    </xsl:for-each>
   </ol>
   </body>
  </html>
 </xsl:template>
</xsl:stylesheet>
Code Explanation

As suggested above, we loop through the City elements outputting list items. With each iteration, the context node is a different City element, which contains the city's name and population, so we can output those directly. The City element also contains the state abbreviation. The trick is to use that to get the full name of the state. This XPath does the trick: //States/State[@Abbr=$State]. It goes back up to the root of the document and looks through the State elements for one that has an abbreviation matching the State attribute (held in a variable) of the City element.

Although this works, it is an intensive process. For each City, the XSLT processor has to make a trip through the node tree looking for a matching State.

The Key Way

Let's take a look at another code sample that uses keys to accomplish the same thing.

Code Sample: Keys/Demos/CitiesWithKey.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes"/>
 <xsl:key name="keyState" match="State" use="@Abbr"/>
 <xsl:template match="/">
  <html>
   <head>
    <title>Largest Cities in the United States</title>
   </head>
   <body>
   <h1>Largest Cities in the United States</h1>
   <ol>
    <xsl:for-each select="//City">
     <li>
      <xsl:value-of select="."/>
      <xsl:text> is in the state of </xsl:text>
      <xsl:value-of select="key('keyState',@State)"/>
      <xsl:text> and has a population of </xsl:text>
      <xsl:value-of select="format-number(@Population,'#,###')"/>
      <xsl:text>.</xsl:text>
     </li>
    </xsl:for-each>
   </ol>
   </body>
  </html>
 </xsl:template>
</xsl:stylesheet>
Code Explanation

In the example above, the key is created with this line:

<xsl:key name="keyState" match="State" use="@Abbr"/>

Imagine that it creates a lookup table that looks something like this:

Index Node-set
AL Alabama State node
AK Alaska State node
AZ Arizona State node
AR Arkansas State node
CA California State node

The key() function is used to look up the nodes specified by the key.

<xsl:value-of select="key('keyState',@State)"/>

If the value of the State attribute in the current City element is found in the lookup table created by the keyState key, then that value is output.

Again, the major reason this is better than the first way is that the XSLT processor creates an index of the nodes, so that it does not have to search through the whole node tree looking for a match.

Grouping

It is often necessary to group elements based on certain attributes or subelements. Consider our XML document that contains the largest cities in America. We might want to output these cities by state as shown below.

Code Sample: Keys/Demos/CityByStateOutput.html

<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
<title>Most Populated Cities by State</title>
</head>
<body>
<h2>1. California - 17 Cities</h2>
<ol>
<li>Los Angeles</li>
<li>San Diego</li>
<li>San Jose</li>
---- Code Omitted ----
</ol> <h2>2. Texas - 12 Cities</h2> <ol> <li>Houston</li> <li>Dallas</li> <li>San Antonio</li>
---- Code Omitted ----
</ol> <h2>3. Arizona - 5 Cities</h2> <ol> <li>Phoenix</li> <li>Tucson</li> <li>Mesa</li>
---- Code Omitted ----
</body> </html>

In XSLT 1.0, there is no tag meant for grouping, but there are at least a couple of ways to get the job done. The most efficient way to do this is to use a method called the Muencian method after Steve Muench who created it. This method requires a good understanding of the generate-id() function discussed below.

The generate-id() Function

The generate-id() function creates ids that uniquely identify a node in an XML document. It takes a single argument: the node-set to identify. The value of the generated id is based on the first node in the node-set, and the same id will be generated no matter how the node is retrieved. To illustrate this, take a look at the following two files.

Code Sample: Keys/Demos/GenerateId.xml

Code Sample: Keys/Demos/GenerateId.xsl

Code Explanation

The generate-id() function is passed five different XPaths and returns the same output each time. Again, this is because the first element in the node-set is always the same.

Code Sample: Keys/Demos/GenerateIdOutput.xml

As shown below, we can use this knowledge to find the first City element with a specific State attribute.

Code Sample: Keys/Demos/BiggestCitiesByState.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes"/>
 <xsl:key name="keyCities" match="City" use="@State"/>
 
 <xsl:template match="/">
  <html>
   <head>
    <title>Most Populated Cities by State</title>
   </head>
   <body>
    <ol>
    <xsl:for-each 
     select="//City[generate-id(.)=generate-id(key('keyCities',@State))]">
     <!--
      The first generate-id() above returns an id for the current City.
      The second generate-id() above returns an id for the first 
       City with a State attribute that is the same as the current 
       City's State attribute.
      These values will only be equal for the first City elements 
       with a specific State attribute.
      So, this for-each loops through the each City first to specify
       a particular State.
     -->
     <li>
      <xsl:value-of select="."/>, <xsl:value-of select="@State"/>
     </li>
    </xsl:for-each>
    </ol>
   </body>
  </html>
 </xsl:template>
 
</xsl:stylesheet>
Code Explanation

As indicated in the comment:

  1. The first generate-id() above returns an id for the current City.
  2. The second generate-id() above returns an id for the first City with a State attribute that is the same as the current City's State attribute.
  3. These values will only be equal for the first City elements with a specific State attribute. So, this for-each loops through each City first to specify a particular State.
  4. As the XML document lists the cities in descending order by population, the most populated cities in each state are returned.

Using generate-id for Grouping

Okay. Now let's get back to grouping. We've learned how to use generate-id() and keys pick just one city from every state. Another way to look at this is that we've learned how to retrieve each distinct state once and only once. To group by state, we need loop through the City elements for each state and return all the cities in that state. But we don't need to go through the whole node tree again, because we have a key that contains all City elements by State. Take a look at the following code.

Code Sample: Keys/Demos/GroupCities.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes"/>
 <xsl:key name="keyCities" match="City" use="@State"/>
 <xsl:key name="keyState" match="State" use="@Abbr"/>
 
 <xsl:template match="/">
  <html>
   <head>
    <title>Most Populated Cities by State</title>
   </head>
   <body>
    <xsl:for-each 
     select="//City[generate-id(.)=generate-id(key('keyCities',@State))]">
     <!--
      The first generate-id() above returns an id for the current City.
      The second generate-id() above returns an id for the first 
       City with a State attribute that is the same as the current 
       City's State attribute.
      These values will only be equal for the first City elements 
       with a specific State attribute.
      So, this for-each loops through the each City first to specify
       a particular State.
     -->
     <xsl:sort select="count(key('keyCities',@State))" 
      order="descending" data-type="number"/>
     <h2>
      <xsl:value-of select="position()"/>
      <xsl:text>. </xsl:text>
      <xsl:value-of select="key('keyState',@State)"/>
      <xsl:text> - </xsl:text>
      <xsl:value-of select="count(key('keyCities',@State))"/>
      <xsl:text> Cities</xsl:text>
     </h2>
     <ol>
      <xsl:for-each select="key('keyCities',@State)">
       <li><xsl:value-of select="."/></li>
      </xsl:for-each>
      <!--
       This nested for-each loops through the City elements that have
        the same value for the State attribute as the current City in
        the outer for-each loop.
      -->
     </ol>
    </xsl:for-each>
   </body>
  </html>
 </xsl:template>
 
</xsl:stylesheet>
Code Explanation

Remember that the key() function returns a node-set. We loop through this node-set with the following code:

<xsl:for-each select="key('keyCities',@State)">
 <li><xsl:value-of select="."/></li>
</xsl:for-each>

We also sort the outer loop by state according to the number of cities per state. This is done by counting the number of City elements matched by the key() function:

<xsl:sort select="count(key('keyCities',@State))" 
      order="descending" data-type="number"/>

Working with Keys Conclusion

In this lesson of the XSLT tutorial, you have learned how to use keys to create efficient look-up tables, do cross referencing, and group output efficiently.

To continue to learn XSLT go to the top of this page and click on the next lesson in this XSLT Tutorial's Table of Contents.

Use of this website implies agreement to the following:

Copyright Information

All pages and graphics on this Web site are the property of Webucator, Inc. unless otherwise specified.

None of the content on this website may be redistributed or reproduced in any way, shape, or form without written permission from Webucator, Inc.

No Printing or saving of web pages

This content may not be printed or saved. It is for online use only.


Linking to this website

You may link to any of the pages on this website; however, you may not include the content in a frame or iframe without written permission from Webucator, Inc.


Warranties

This website is provided without warranty of any kind. There are no guarantees that use of the site will not be subject to interruptions. All direct or indirect risk related to use of the site is borne entirely by the user. All code and explanations provided on this site are provided without warranties to correctness, performance, fitness, merchantability, and/or any other warranty (whether expressed or implied).

For individual private use only

You agree not to use this online manual to deliver or receive training. If you are delivering or attending a class that is making use of this online manual, you are in violation of our terms of service. Please report any abuse to courseware@webucator.com. If you would like to deliver or receive training using this manual, please fill out the form at http://www.webucator.com/Contact.cfm.