Chapter 14

Keys and Cross-Referencing

In This Chapter

bullet Finding out about cross-referencing

bullet Using xsl:key and key() together

bullet Using keys with multiple source documents

I remember the first time I ever heard the term cross-reference. I was in school, scouring through the library’s card catalogue, trying to find some obscure book on Ancient Greece. With today’s technology, cross-referencing has been transformed from the kind of labor-intensive process that I faced in that dusty ol’ library into a simple, point-and-click motion on a computer. (Did I also tell you that I had to walk two miles to school each day in a foot of snow in bare feet? Well, I tell my kids that anyway.)

The Web is built on top of this notion of cross-referencing. The reason is obvious: Storing all the information available on the Web in a single gigantic page makes no sense, of course, because such a page would be unusable. Instead, related Web pages are linked together using hypertext.

Relational databases, such as Microsoft Access, are another example of how you can effectively cross-reference related information. For people who need to work with even small amounts of data, storing data across multiple tables is easier to maintain than dumping everything into a single massive table.

In case you’re unfamiliar with databases, the basic notion of a relational database is to store the information in the place that makes the most sense. For example, you may store customers’ information, orders, and parts into separate databases. And, then, when you want to use this information, you can link the related pieces together for the task you wish to perform.

It follows, then, that for XSLT to be flexible and useable for a variety of purposes, it also needs to offer a similar cross-referencing device. Otherwise, you could never bring together elements from disparate XML structures and integrate them in a result document. Instead, you’d be forced to create huge monolithic XML elements that would contain every possible piece of data in it, including the kitchen sink. In this chapter, you find out about how XSLT uses keys that enable you to cross-reference your source documents.

Keys to the Kingdom

You cross-reference in XSLT by using a key. No, not the kind of key you use to start your car, but something far different; in XSLT, a key is a point of access that you can use to connect one data set to another, similar to the way a hypertext link connects two HTML documents.

Tip

If you are unfamiliar with the term key, then it is helpful to think of it in the same way as a hyperlink in an HTML file. Although the two terms aren’t synonymous, the comparison is helpful when you’re beginning. The hypertext link provides a point of access that another document references so that people can obtain more information from the target document. So too, an XSLT key gives you a means to access more information from a node set.

The notches in a key

Keys are used in XSLT to cross-reference through a combination of the xsl:key element and the key() built-in function; xsl:key defines the key and key() references it.

Looking closer, the xsl:key element is used to define a key for a node set. Its syntax is:

<xsl:key name=”keyname” match=”pattern” use=”expression”/>

This element has three attributes:

bullet name defines the name of the key. This label is used to reference the key elsewhere in the stylesheet.

bullet match specifies an XPath pattern, typically returning a node set that contains the key.

bullet use is an expression that names the value of the key.

TechnicalStuff

If you’ve worked with relational databases before, you’ve obviously worked with key fields in a database table. An xsl:key element that you define in your stylesheet works in much the same way.

To demonstrate how to use keys, consider the following states document:

  <states region=”New England”>

    <state id=”01”>Maine</state>

    <state id=”02”>New Hampshire</state>

    <state id=”03”>Vermont</state>

    <state id=”04”>Massachusetts</state>

    <state id=”05”>Connecticut</state>

    <state id=”06”>Rhode Island</state>

  </states>

I’d like to define a key on this document so that, when a state element’s id attribute is requested, the content is returned. I can declare this key with the following element:

 <xsl:key name=”StateKey” match=”state” use=”@id”/>

In this statement, the StateKey key returns all the state elements, specifying that the id attribute of the returning nodes in the node set is the key to use.

Linking with the key() function

The xsl:key element is ready, but it can’t do anything on its own. You need to use the key() built-in function to actually do something with it. The key() function syntax is:

 key(KeyName, LookupValue)

The key() function has two parameters:

bullet KeyName specifies the name of the xsl:key element you wish to use.

bullet LookupValue is an expression that provides a value to look up.

Given this syntax, if I want to return the value of Rhode Island, I can use the following code:

 key(‘StateKey’, ‘06’)

Although putting a literal text value can be useful on occasion, what makes the key() function powerful is when the LookupValue of the key() function is the value of another element or attribute. To demonstrate, mull over the following source document:

 <?xml version=”1.0”?>

<customers>

  <states region=”New England”>

    <state id=”01”>Maine</state>

    <state id=”02”>New Hampshire</state>

    <state id=”03”>Vermont</state>

    <state id=”04”>Massachusetts</state>

    <state id=”05”>Connecticut</state>

    <state id=”06”>Rhode Island</state>

  </states>

  <customer id=”C3020” stateid=”04”>Bridget McFarland</customer>

  <customer id=”C3021” stateid=”04”>Cheri Burrer</customer>

  <customer id=”C3022” stateid=”02”>Greg Stephenson</customer>

  <customer id=”C3023” stateid=”05”>Mark Horine</customer>

  <customer id=”C3024” stateid=”01”>Don Shafer</customer>

  <customer id=”C3025” stateid=”04”>Leo Minster</customer>

</customers>

Rather than cramming state-related information into the customer element, the preceding structure separates this information and places it in the separate states element. However, to provide a link between a customer and the state of residence, each of the customer elements have a stateid that references the id attribute of one of the state elements. For example, Bridget McFarland has a stateid of 04, which refers to the Massachusetts state element.

The xsl:key element is designed to bridge these two worlds, or source documents. The following stylesheet does just that, by using a key to print out the state for each customer:

 <?xml version=”1.0”?>

<xsl:stylesheet version=”1.0” xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>

  <xsl:output method=”text”/>

  <!-- Define key -->

  <xsl:key name=”StateKey” match=”state” use=”@id”/>

  <!-- Plug in key value -->

  <xsl:template match=”customer”>

    <xsl:apply-templates/> lives in <xsl:value-of select=”key(‘StateKey’, @stateid)”/>

    <xsl:text>.</xsl:text>

  </xsl:template>

  <!-- Empty template -->

  <xsl:template match=”states”/>

</xsl:stylesheet>

After defining the xsl:key element, a customer template rule adds the customer name by using xsl:apply-templates and then adds the key() function to return the state name for that customer. Instead of using a string value as the lookup value, key() uses the expression @stateid, so that for each customer node, its stateid attribute is used as the lookup value. Finally, to prevent the states elements from being displayed outside of my purposes, I define an empty template rule. The resulting document is as follows:

  Bridget McFarland lives in Massachusetts.

  Cheri Burrer lives in Massachusetts.

  Greg Stephenson lives in New Hampshire.

  Mark Horine lives in Connecticut.

  Don Shafer lives in Maine.

  Leo Minster lives in Massachusetts.

Working with multiple keys

Keys can be used in a multitude of ways to draw together data from a variety of different XML structures. Check out the orderprocess document, which contains states, parts, customers, and orders substructures:

 <?xml version=”1.0”?>

<orderprocess>

  <states region=”New England”>

    <state id=”01”>Maine</state>

    <state id=”02”>New Hampshire</state>

    <state id=”03”>Vermont</state>

    <state id=”04”>Massachusetts</state>

    <state id=”05”>Connecticut</state>

    <state id=”06”>Rhode Island</state>

  </states>

  <customers>

    <customer id=”C3020” stateid=”04”>Bridget McFarland</customer>

    <customer id=”C3021” stateid=”04”>Cheri Burrer</customer>

    <customer id=”C3022” stateid=”02”>Greg Stephenson</customer>

    <customer id=”C3023” stateid=”05”>Mark Horine</customer>

    <customer id=”C3024” stateid=”01”>Don Shafer</customer>

    <customer id=”C3025” stateid=”04”>Leo Minster</customer>

  </customers>

  <parts>

    <part id=”120” price=”3.99”>Baseball</part>

    <part id=”121” price=”8.99”>Basketball</part>

    <part id=”122” price=”6.99”>Football</part>

    <part id=”123” price=”7.99”>Soccer Ball</part>

    <part id=”124” price=”960.99”>Football Goalpost</part>

    <part id=”125” price=”340000000”>Outdoor Football Stadium</part>

    <part id=”126” price=”160.99”>Foosball Table</part>

    <part id=”127” price=”899.99”>Road Bicycle</part>

    <part id=”128” price=”799.99”>ATB Bicycle</part>

    <part id=”129” price=”12.99”>Baseball Bat</part>

    <part id=”130” price=”19.99”>Basketball Goal</part>

  </parts>

  <orders>

    <order custid=”C3020” partid=”120” quantity=”1”/>

    <order custid=”C3021” partid=”124” quantity=”3”/>

    <order custid=”C3021” partid=”126” quantity=”2”/>

    <order custid=”C3020” partid=”125” quantity=”1”/>

    <order custid=”C3024” partid=”126” quantity=”1”/>

    <order custid=”C3024” partid=”123” quantity=”12”/>

    <order custid=”C3022” partid=”127” quantity=”2”/>

    <order custid=”C3022” partid=”128” quantity=”3”/>

    <order custid=”C3022” partid=”130” quantity=”2”/>

    <order custid=”C3021” partid=”130” quantity=”1”/>

    <order custid=”C3021” partid=”126” quantity=”7”/>

  </orders>

</orderprocess>

My objective is to create an order summary report, providing summary information about each order. The order elements bring all the related data together, but its primary task is to reference the other XML structures rather than to contain the actual data. Specifically, for each order, I want to list five pieces of data: customer name, state that the order is shipping to, part ordered, quantity, unit price, and total price.

Because portions of this information are in the other XML structures, I define three keys for the state, customer, and part elements:

  <xsl:key name=”StateKey” match=”state” use=”@id”/>

  <xsl:key name=”CustomerKey” match=”customer” use=”@id”/>

  <xsl:key name=”PartKey” match=”part” use=”@id”/>

Inside a template rule that uses order as the match pattern, I reference the CustomerKey key by using the custid of the current order in the returning node set to retrieve the customer name:

 <xsl:template match=”order”>

   <xsl:value-of select=”key(‘CustomerKey’, @custid)”/>

Retrieving the customer’s state is trickier, because the order element doesn’t include any state-related links. Therefore, I need to perform a double look up — first, look up the matching customer element and, second, look up his or her state by using the following instruction:

<xsl:value-of select=”key(‘StateKey’, key(‘CustomerKey’, @custid)/@stateid )”/>

The part information is retrieved through a link between the PartKey key and the order element’s partid attribute:

<xsl:value-of select=”key(‘PartKey’, @partid)”/>

The quantity and unit price values are actually used twice, because the total cost of the order involves multiplying the quantity by the unit price. Rather than retrieve this information multiple times in my stylesheet, I create two variables that hold these values for me because this is more efficient:

  <xsl:variable name=”qty” select=”quantity”/>

  <xsl:variable name=”unitprice” select=”key(‘PartKey’, @partid)/@price”/>

The qty variable gets the value of the order element’s quantity attribute, while the unitprice variable retrieves this pricing data by first getting the part through the PartKey key and then returning the price attribute of the matching node:

  <xsl:variable name=”qty” select=”quantity”/>

  <xsl:variable name=”unitprice” select=”key(‘PartKey’, @partid)/@price”/>

These variables can then be simply plugged in:

<xsl:value-of select=”number($qty)”/>

<xsl:value-of select=”format-number( $unitprice, ‘#,###.##’ )”/>

<xsl:value-of select=”format-number( $qty*$unitprice, ‘#,###.##’ )”/>

Here’s what the complete XSLT stylesheet looks like:

<?xml version=”1.0”?>

<xsl:stylesheet version=”1.0” xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>

  <xsl:output method=”text”/>

  <!-- Define keys -->

  <xsl:key name=”StateKey” match=”state” use=”@id”/>

  <xsl:key name=”CustomerKey” match=”customer” use=”@id”/>

  <xsl:key name=”PartKey” match=”part” use=”@id”/>

  <!-- Order summary -->

  <xsl:template match=”order”>

  -------------------------------------------

  Order Summary

  

  Customer: <xsl:value-of select=”key(‘CustomerKey’, @custid)”/>

  Ship To: <xsl:value-of select=”key(‘StateKey’, key(‘CustomerKey’, @custid)/@stateid )”/>

  Part: <xsl:value-of select=”key(‘PartKey’, @partid)”/>

  <xsl:variable name=”qty” select=”@quantity”/>

  <xsl:variable name=”unitprice” select=”key(‘PartKey’, @partid)/@price”/>

  Quantity: <xsl:value-of select=”number($qty)”/>

  Unit Price: $<xsl:value-of select=”format-number( $unitprice, ‘#,###.##’ )”/>

  Total Price: $<xsl:value-of select=”format-number( $qty*$unitprice, ‘#,###.##’ )”/>

  ---------------------------------------------

  </xsl:template>

  <!-- Empty templates -->

  <xsl:template match=”state”/>

  <xsl:template match=”states”/>

  <xsl:template match=”parts”/>

  <xsl:template match=”customer”/>

</xsl:stylesheet>

The result of this transformation is a text output report providing a summary for each order in the orderprocess structure:

  -------------------------------------------

  Order Summary

  

  Customer: Bridget McFarland

  Ship To: Massachusetts

  Part: Baseball

  Quantity: 1

  Unit Price: $3.99

  Total Price: $3.99

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Cheri Burrer

  Ship To: Massachusetts

  Part: Football Goalpost

  Quantity: 3

  Unit Price: $960.99

  Total Price: $2,882.97

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Cheri Burrer

  Ship To: Massachusetts

  Part: Foosball Table

  Quantity: 2

  Unit Price: $160.99

  Total Price: $321.98

  ---------------------------------------------

    

  -------------------------------------------

  Order Summary

  

  Customer: Bridget McFarland

  Ship To: Massachusetts

  Part: Outdoor Football Stadium

  Quantity: 1

  Unit Price: $340,000,000

  Total Price: $340,000,000

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Don Shafer

  Ship To: Maine

  Part: Foosball Table

  Quantity: 1

  Unit Price: $160.99

  Total Price: $160.99

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Don Shafer

  Ship To: Maine

  Part: Soccer Ball

  Quantity: 12

  Unit Price: $7.99

  Total Price: $95.88

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Greg Stephenson

  Ship To: New Hampshire

  Part: Road Bicycle

  Quantity: 2

  Unit Price: $899.99

  Total Price: $1,799.98

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Greg Stephenson

  Ship To: New Hampshire

  Part: ATB Bicycle

  Quantity: 3

  Unit Price: $799.99

  Total Price: $2,399.97

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Greg Stephenson

  Ship To: New Hampshire

  Part: Basketball Goal

  Quantity: 2

  Unit Price: $19.99

  Total Price: $39.98

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Cheri Burrer

  Ship To: Massachusetts

  Part: Basketball Goal

  Quantity: 1

  Unit Price: $19.99

  Total Price: $19.99

  ---------------------------------------------

  

    

  -------------------------------------------

  Order Summary

  

  Customer: Cheri Burrer

  Ship To: Massachusetts

  Part: Foosball Table

  Quantity: 7

  Unit Price: $160.99

  Total Price: $1,126.93

  ---------------------------------------------

Using Keys with Multiple Source Documents

The preceding example illustrates how you can use keys to link different XML structures, but it did so within a single XML document. In the real world, you often want to use keys that combine data from various XML files. You can do this by using the document() built-in function. This function allows you to include node sets from external files.

As an example, I separate the state-related elements from the customer elements, giving me two source files, Listing 14-1 and Listing 14-2.

Listing 14-1: key_state.xml

<?xml version=”1.0”?>

<!-- key_state.xml -->

<states region=”New England”>

  <state id=”01”>Maine</state>

  <state id=”02”>New Hampshire</state>

  <state id=”03”>Vermont</state>

  <state id=”04”>Massachusetts</state>

  <state id=”05”>Connecticut</state>

  <state id=”06”>Rhode Island</state>

</states>

Listing 14-2: key_customers.xml

<?xml version=”1.0”?>

<!-- key_customers.xml -->

<customers>

  <customer id=”C3020” stateid=”04”>Bridget McFarland</customer>

  <customer id=”C3021” stateid=”04”>Cheri Burrer</customer>

  <customer id=”C3022” stateid=”02”>Greg Stephenson</customer>

  <customer id=”C3023” stateid=”05”>Mark Horine</customer>

  <customer id=”C3024” stateid=”01”>Don Shafer</customer>

  <customer id=”C3025” stateid=”04”>Leo Minster</customer>

</customers>

In the result document, suppose I’d like to provide a simple listing of the customer and his or her state as I do in the initial example of the chapter. To do so, in my XSLT stylesheet, I can define a StatesLookup variable to be the returning node set of the document(‘key_state.xml’) function:

  <xsl:variable name=”StatesLookup” select=”document(‘key_state.xml’)”/>

Therefore, when I plug in the StatesLookup variable in my stylesheet, it references the states element and its contents.

A key is defined for the state elements from the outside file:

  <xsl:key name=”StateKey” match=”state” use=”@id”/>

To perform the key lookup on these elements from another document, I need to set up an xsl:for-each loop to iterate through each of the state elements, calling key() each time to do the lookup. Because the lookup value for the key is the stateid attribute for the customer element, I package it all in a customer template rule:

  <xsl:template match=”customer”>

    <xsl:variable name=”custstate” select=”@stateid”/>

    <xsl:variable name=”custname” select=”.”/>

    <xsl:for-each select=”$StatesLookup”>

      <xsl:value-of select=”$custname”/> lives in <xsl:value-of select=”key(‘StateKey’, $custstate)”/>

      <xsl:text>.  </xsl:text>

    </xsl:for-each>

  </xsl:template>

The entire XSLT stylesheet is as follows:

<?xml version=”1.0”?>

<xsl:stylesheet version=”1.0” xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>

  <xsl:output method=”text”/>

  

  <!-- Assign contents of key_state.xml to var -->

  <xsl:variable name=”StatesLookup” select=”document(‘key_state.xml’)”/>

  <!-- Define key -->

  <xsl:key name=”StateKey” match=”state” use=”@id”/>

  <!-- Plug in key value -->

  <xsl:template match=”customer”>

    <xsl:variable name=”custstate” select=”@stateid”/>

    <xsl:variable name=”custname” select=”.”/>

    <xsl:for-each select=”$StatesLookup”>

      <xsl:value-of select=”$custname”/> lives in <xsl:value-of select=”key(‘StateKey’, $custstate)”/>

      <xsl:text>.  </xsl:text>

    </xsl:for-each>

  </xsl:template>

</xsl:stylesheet>

When the stylesheet is applied to the key customers.xml file (refer to Listing 14-2), the result from the transformation is shown here:

Bridget McFarland lives in Massachusetts.

Cheri Burrer lives in Massachusetts.

Greg Stephenson lives in New Hampshire.

Mark Horine lives in Connecticut.

Don Shafer lives in Maine.

Leo Minster lives in Massachusetts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset