XSLT for Dummies

 
Chapter 14 - Keys and Cross-Referencing
XSLT For Dummies
by Richard Wagner
Hungry Minds 2002
  

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 arent synonymous, the comparison is helpful when youre 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:

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

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

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

 Technical Stuff   If youve worked with relational databases before, youve 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>

Id like to define a key on this document so that, when a state elements 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 cant 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:

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

  • 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 customers state is trickier, because the order element doesnt include any state-related links. Therefore, I need to perform a double look upfirst, 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 elements 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 elements 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, '#,###.##')"/>

Heres 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: .99 Total Price: .99 --------------------------------------------- ------------------------------------------- Order Summary Customer: Cheri Burrer Ship To: Massachusetts Part: Football Goalpost Quantity: 3 Unit Price: 0.99 Total Price: ,882.97 --------------------------------------------- ------------------------------------------- Order Summary Customer: Cheri Burrer Ship To: Massachusetts Part: Foosball Table Quantity: 2 Unit Price: 0.99 Total Price: 1.98 --------------------------------------------- ------------------------------------------- Order Summary Customer: Bridget McFarland Ship To: Massachusetts Part: Outdoor Football Stadium Quantity: 1 Unit Price: 0,000,000 Total Price: 0,000,000 --------------------------------------------- ------------------------------------------- Order Summary Customer: Don Shafer Ship To: Maine Part: Foosball Table Quantity: 1 Unit Price: 0.99 Total Price: 0.99 --------------------------------------------- ------------------------------------------- Order Summary Customer: Don Shafer Ship To: Maine Part: Soccer Ball Quantity: 12 Unit Price: .99 Total Price: .88 --------------------------------------------- ------------------------------------------- Order Summary Customer: Greg Stephenson Ship To: New Hampshire Part: Road Bicycle Quantity: 2 Unit Price: 9.99 Total Price: ,799.98 --------------------------------------------- ------------------------------------------- Order Summary Customer: Greg Stephenson Ship To: New Hampshire Part: ATB Bicycle Quantity: 3 Unit Price: 9.99 Total Price: ,399.97 --------------------------------------------- ------------------------------------------- Order Summary Customer: Greg Stephenson Ship To: New Hampshire Part: Basketball Goal Quantity: 2 Unit Price: .99 Total Price: .98 --------------------------------------------- ------------------------------------------- Order Summary Customer: Cheri Burrer Ship To: Massachusetts Part: Basketball Goal Quantity: 1 Unit Price: .99 Total Price: .99 --------------------------------------------- ------------------------------------------- Order Summary Customer: Cheri Burrer Ship To: Massachusetts Part: Foosball Table Quantity: 7 Unit Price: 0.99 Total Price: ,126.93 ---------------------------------------------

  
 
 
2000-2002    Feedback

Категории