Chapter 6

We Want Results!

In This Chapter

bullet Ease versus flexibility

bullet Copying elements

bullet Adding and renaming elements

bullet Adding calculated values

bullet Removing elements and attributes

bullet Reordering and merging elements

bullet Adding and moving attributes

bullet Converting elements into attributes and attributes into elements

S ports fans are a fickle sort. When their team is winning, the coach is a genius, the team leader is a superman, and the players are the toast of the town. But when the local team starts to lose, suddenly these one-time heroes become the focus of scorn and contempt, especially on those radio call-in shows. When it comes to their favorite team, fans want results.

In the same way, people like you and me come to XSLT for one thing: results. More than most other languages, XSLT is a highly focused, goal-oriented language. You don’t use it to create games or expansive systems; you use it because you have XML and need to transform it into something else, perhaps another XML format, HTML, text, or whatever.

After you discover many of the basics of XSLT, it’s time to get down to business. This chapter does just that by providing practical examples that show you how to perform the most common types of transformations using XSLT.

XSLT, Like Skinning a Cat

I’m sure you’ve heard the old expression, “There’s more than one way to skin a cat.” Although that adage may not make cat lovers like me happy (and may send my two cats hiding under the bed), it sure does apply to XSLT. As you grow more and more comfortable with the language, you find out that more than one way exists to generate the result you’re seeking. What’s more, for many tasks, there is no definitive right or wrong way to produce a transformed document.

Having said that, a seasoned XSLT developer knows that, in many instances, a specific technique may be more suitable or advantageous than another. Most of the time the decision comes down to a question of ease versus flexibility: Some methods are easier to do while others give you more power and flexibility.

Consider the following simple example. You can create a new element by using a literal text string within a template rule:

<cat>Siamese</cat>

But you can also get identical results by using the xsl:element instruction:

<xsl:element name=”cat”>Siamese</xsl:element>

Given that, why does XSLT have two or more methods that produce the same result? And which is the preferred way? For most purposes, the literal text approach is perfectly acceptable and is certainly the easiest to read and write. In contrast, the xsl:element instruction gives you more flexibility by allowing you to name an element, not just from a text string, but also to base it on the result of an XPath expression. Like everything else in life, knowing which technique is the most suitable simply comes with experience.

XML Source

To help you follow the XSLT stylesheet examples in this chapter, each example uses the coffee.xml source document shown in Listing 6-1 or, as appropriate, the smaller coffee-light.xml shown in Listing 6-2. Turn back to these code listings when you want to refer to the source.

Listing 6-1: Coffee.xml

<?xml version=”1.0”?>

<!-- coffee.xml -->

<coffees>

 <region name=”Latin America”>

  <coffee name=”Guatemalan Express” origin=”Guatemala”>

    <taste>Curiously Mild And Bland</taste>

    <price>11.99</price>

    <availability>Year-round</availability>

    <bestwith>Breakfast</bestwith>

</coffee>

  <coffee name=”Costa Rican Deacon” origin=”Costa Rica”>

    <taste>Solid Yet Understated</taste>

    <price>12.99</price>

    <availability>Year-round</availability>

    <bestwith>Dessert</bestwith>

  </coffee>

 </region>

 <region name=”Africa”>

  <coffee name=”Ethiopian Sunset Supremo” origin=”Ethiopia”>

    <taste>Exotic And Untamed</taste>

    <price>14.99</price>

    <availability>Limited</availability>

    <bestwith>Chocolate</bestwith>

  </coffee>

  <coffee name=”Kenyan Elephantismo” origin=”Kenya”>

    <taste>Thick And Chewy</taste>

    <price>9.99</price>

    <availability>Year-round</availability>

    <bestwith>Elephant Ears</bestwith>

  </coffee>

  </region>

</coffees>

Listing 6-2: Coffee-light.xml

 <?xml version=”1.0”?>

<!-- coffee-light.xml -->

<coffee name=”Guatemalan Express” origin=”Guatemala”>

  <taste>Curiously Mild And Bland</taste>

  <price>11.99</price>

  <availability>Year-round</availability>

  <bestwith>Breakfast</bestwith>

</coffee>

Pretty in pink?

The result documents in this chapter are formatted in a way that maximizes readability. Therefore, in many cases, when you run the stylesheet shown in the examples, the output looks slightly different, in terms of where line breaks or indenting occur. Don’t worry about this for now; focus on the content first and later you can pretty up the results. When it comes to XML output, formatting is irrelevant more often than not, and is usually added only for readability. Having said that, I discuss how to format output in full detail in Chapter 13.

Copying an Element

XSLT is all about moving information from one place to another. Within all this hustle and bustle, the element is at the center of this activity. After all, most everything you move into the new document is an element or comes from an element in some way. When you want to perform a simple copy of an element’s content or tags or even copy everything from one tree to another, use the techniques I discuss in the following sections.

Content only

In previous chapters, you find out that xsl:apply-templates, by calling the element’s built-in templates, is used to push an element’s content to the result tree, stripping away its tags in the process. I can use this technique to output just the content of the price element in the coffee.xml document by using the following stylesheet:

  <!-- coffee-copy_elementcontent.xsl --> 

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

  <!-- Add just content -->

  <xsl:template match=”price”><xsl:text>

</xsl:text>

    <xsl:apply-templates/>

  </xsl:template>

  <!-- Show just prices -->

  <xsl:template match=”coffees”>

      <xsl:apply-templates select=”region/coffee/price”/>

  </xsl:template>

</xsl:stylesheet>

The purpose of the first template rule is fairly simple; it outputs the price e lement’s content. Using the xsl:text instruction, a line break is added before each line to separate the numbers that are generated in the transformation.

Although this template rule can produce the results I’m looking for, I have to keep in mind the context of the price element — it is a child of the coffee element, which is a child of region, which is a child of coffees. As a result, if I only use the price template rule in the stylesheet, I am going to get additional content in my result document that I’m not expecting. In case you’re wondering why, remember the built-in template rules (which I discuss in Chapter 4) that are run on each transformation — an xsl:apply-templates is run by default on any element that doesn’t have an explicitly defined template rule.

To prevent built-in templates from kicking in on the other elements, a second template rule is added that uses the coffees document element as the match pattern. However, I can’t just create an empty template rule (<xsl:element match=”coffees”/>) because it would override the built-in template rules not only for coffees but also its descendants as well. In essence, it would suffocate the price template rule I just defined. So, in this particular case, the easiest solution is to make sure that the xsl:apply-templates instruction is applied only to the price element by using region/coffee/price as the select attribute value.

The result that is generated is shown here:

11.99

12.99

14.99

9.99

Tags only

The xsl:copy instruction is one of two copy routines that XSLT has in its arsenal. I always consider it the decaffeinated version of the two, because it doesn’t copy children or attributes and, by default, doesn’t copy content either. Suppose you need to copy only the tags of a given element. If so, xsl:copy is a good candidate for the job. In the following code, my goal is to do the inverse of the preceding example: Output just the tags, not the content. To do so, I create a stylesheet that looks like the following:

 <!-- coffee-copy_elementcontent.xsl --> 

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

  <!-- Copy just tag -->

  <xsl:template match=”price”>

    <xsl:copy/>

  </xsl:template>

  <!-- Add parent, run on just price -->

  <xsl:template match=”coffees”>

    <prices>

      <xsl:apply-templates select=”region/coffee/price”/>

    </prices>

  </xsl:template>

</xsl:stylesheet>

The price template rule contains an empty xsl:copy instruction to tell the XSLT processor to copy just the tags. Also, because I don’t want any other parts of the coffee.xml document carried over, I use a similar template rule for coffees that I used in the previous example. However, because my result document is XML this time, I am going to add a prices document element into the result tree by adding literal text strings both before and after the xsl:apply-templates instruction. When an XSLT processor runs this stylesheet, the results generated are:

<?xml version=”1.0” encoding=”utf-8”?>

<prices>

  <price/>

  <price/>

  <price/>

  <price/>

</prices>

Copy all

If xsl:copy is the decaffeinated side of XSLT copying, xsl:copy-of must be like a venti-sized coffee at Starbucks: It packs a wallop! xsl:copy-of is surely the tool of choice when you want to copy the entire element and its various pieces. To copy both the tags and content of the price element, I can use the following XSLT code:

<!-- coffee-copy_elementfull.xsl -->

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

  <!-- Copy price element -->

  <xsl:template match=”price”>

    <xsl:copy-of select=”.”/>

  </xsl:template>

  

  <!-- Add parent, run on just price -->

  <xsl:template match=”coffees”>

    <prices>

      <xsl:apply-templates select=”region/coffee/price”/>

    </prices>

  </xsl:template>

</xsl:stylesheet>

The XML output produced is shown here:

<?xml version=”1.0” encoding=”utf-8”?>

<prices>

  <price>11.99</price>

  <price>12.99</price>

  <price>14.99</price>

  <price>9.99</price>

</prices>

A second way to copy an element is through a method that can be called reconstruction. The gist of this method is that you use a combination of literal text and xsl:value instructions to reconstruct the pieces of the element. As shown in the following XSLT stylesheet, the price template rule uses the reconstruction technique to achieve identical results as the preceding xsl:copy-of example:

<!-- coffee-copy_elementrecreate.xsl -->

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

  <!-- Recreate element -->

  <xsl:template match=”price”>

    <price><xsl:value-of select=”.”/></price>

  </xsl:template>

  <!-- Add parent, run on just price -->

  <xsl:template match=”coffees”>

    <prices>

      <xsl:apply-templates select=”region/coffee/price”/>

    </prices>

  </xsl:template>

</xsl:stylesheet>

Copying All Elements

To copy the entire set of elements in a document, you can use the same xsl:copy-of instruction that you use to copy a single element and its children. To copy the whole enchilada, use the xsl:copy-of instruction within the context of the root node (using / as the match pattern):

<!-- coffee-copy_tree.xsl -->

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

  <xsl:template match=”/”>

    <xsl:copy-of select=”.”/>

  </xsl:template>

</xsl:stylesheet>

When this template rule is run, the output looks identical to the original coffee.xml document tree. xsl:copy-of even brings along processing instructions and comments for the ride. Now that’s service!

Adding a New Element

You can add a new element to the result tree by using one of two methods: literal text or the xsl:element instruction.

Using literal text

The simplest technique for creating a new element in your output is to simply create it in your stylesheet as literal text. Suppose, for example, that I want to add an online element as a child to the coffee element. This new element is used to indicate whether a particular type of coffee is available as an online purchase. In this example, all the coffees are defined the same, so I can define the element’s content of Yes as literal text too.

I begin by showing you the long version for this process and then explain a more efficient technique after that. For starters, here is a stylesheet that can be used for this transformation:

<!-- coffee-addelement_literal.xsl -->  

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

  <!-- Add new element -->

  <xsl:template match=”coffee”>

    <coffee>

      <taste><xsl:value-of select=”taste”/></taste>

      <price><xsl:value-of select=”price”/></price>

      <availability><xsl:value-of select=”availability”/></availability>

      <bestwith><xsl:value-of select=”taste”/></bestwith>

      <online>Yes</online>

    </coffee>

  </xsl:template>

  <!-- Recreate the top level node -->

  <xsl:template match=”/”>

    <coffees>

      <xsl:apply-templates/>

    </coffees>

  </xsl:template>

  

</xsl:stylesheet>

The coffee template rule uses the reconstruction method I showed you earlier — entering literal text to define the element tags and xsl:value-of to bring in the content of the matching child element from the source tree. The online element is simply added onto the end.

To retain coffees as the document element, I recreate it with the second template rule. Its purpose is to use xsl:apply-templates to apply to every element, surrounding those results with a coffees tag pair. (Remember, I need to add these tags back into the result document, because the built-in template rules automatically strip them out.) After the transformation is performed, the following result is produced:

<?xml version=”1.0” encoding=”utf-8”?>

<coffees>

  <coffee>

    <taste>Curiously Mild And Bland</taste>

    <price>11.99</price>

    <availability>Year-round</availability>

    <bestwith>Breakfast</bestwith>

    <online>Yes</online>

  </coffee>

  <coffee>

    <taste>Exotic and Untamed</taste>

    <price>12.99</price>

    <availability>Year-round</availability>

    <bestwith>Dessert</bestwith>

    <online>Yes</online>

  </coffee>

  <coffee>

    <taste>Exotic and Untamed</taste>

    <price>14.99</price>

    <availability>Limited</availability>

    <bestwith>Chocolate</bestwith>

    <online>Yes</online>

  </coffee>

  <coffee>

    <taste>Solid Yet Understated</taste>

    <price>9.99</price>

    <availability>Year-round</availability>

    <bestwith>Elephant Ears</bestwith>

    <online>Yes</online>

  </coffee>

</coffees>

Tip

The coffee template rule contains both literal text elements as well as XSLT instructions. A logical question you may be asking curiously is, “How does the XSLT processor know a literal element from an XSLT command?” The answer lies in the namespace: It treats all elements outside the xsl namespace as plain text and avoids trying to process them during transformations.

Although the previous example is perfectly valid and produces the intended results, it sure is a lot of work to reconstruct several elements that don’t change at all from the source to the result document. A much more efficient way to do the same task is as follows:

<!-- coffee-addelement_literal2.xsl --> 

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

  <!-- Add new element -->

  <xsl:template match=”coffee”>

    <coffee>

      <xsl:apply-templates/>

      <online>Yes</online>

    </coffee>

  </xsl:template>

  <!-- Copy everything else over -->

  <xsl:template match=”@*|node()”>

    <xsl:copy>

      <xsl:apply-templates select=”@*|node()”/>

    </xsl:copy>

  </xsl:template>

  

</xsl:stylesheet>

This stylesheet produces the exact same result tree as the previous example. It does so by defining two template rules. The coffee template rule focuses on recreating the coffee element tags and adding the new online element. These literal text nodes are added to the result tree using xsl:applytemplates.

The second template rule is a nifty catchall template that is used to copy everything else over into the result document. It allows you to ignore the children elements that don’t change as you create the coffee template rule.

Using xsl:element

In addition to adding elements using literal text, XSLT provides a specific instruction for adding elements: xsl:element. The xsl:element uses a name attribute to define the element’s name while the content of the instruction defines the value. For example:

<xsl:element name=”dog”>Sheepdog</xsl:element>

This code produces the following element:

<dog>Sheepdog</dog>

I can use xsl:element to achieve the same result as with the previous example, simply by substituting the literal text for an xsl:element instruction:

<!-- coffee-addelement_instr.xsl -->

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

  <!-- Add new element using xsl:element -->

  <xsl:template match=”coffee”>

    <coffee>

      <xsl:apply-templates/>

      <xsl:element name=”online”>Yes</xsl:element>

    </coffee>

  </xsl:template>

  <!-- Copy everything else over -->

  <xsl:template match=”@*|node()”>

    <xsl:copy>

      <xsl:apply-templates select=”@*|node()”/>

    </xsl:copy>

  </xsl:template>

</xsl:stylesheet>

Adding a Calculated Value

If you have used Excel or any database like Access, chances are you have used calculated values before. A calculated value is a number that is produced through some sort of calculation. (Here’s where all that math you learned in school kicks in.) Placing a calculation within an XPath expression, you can create a calculated value for your results document.

Suppose, for example, that I want to add a new child element for the coffee element that provides a discounted price for the coffee. In this case, I’d like to add a new discountprice element that is 20 percent off the price element value. The stylesheet is set up as follows:

<!-- coffee-addelement_calc.xsl -->

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

  <!-- Add new calculated element -->

  <xsl:template match=”coffee”>

    <coffee>

      <xsl:apply-templates/>

      <discountprice><xsl:value-of select=”format-number( price*.8, ‘##.##’ )”/></discountprice>

    </coffee>

  </xsl:template>

  

  <!-- Copy everything else over -->

  <xsl:template match=”@*|node()”>

    <xsl:copy>

      <xsl:apply-templates select=”@*|node()”/>

    </xsl:copy>

  </xsl:template>

</xsl:stylesheet>

The first template rule I created uses coffee as the match pattern to return the coffee element node set. Literal text is used to define the discountprice tags. Because the value of the element needs to be generated during processing time, I need to use an XPath expression to generate the value. An xsl:value-of instruction comes in handy at this point, because you can use it to convert the result of an XPath expression into a string.

To get the value I’m looking for, I can use a simple expression price * .8. However, because this value needs to represent a currency amount, I need to round the value to two decimal places. To do that, XPath conveniently has a built-in function called format-number that allows you to define how you want to format a number output. I talk more about format-number in Chapter 11, but the important thing to know for this example is that the function has two parameters: The first is the number to format (price*.8) and the second is the format picture (##.##, where # represents a numeric digit).

The results from the transformation are as follows:

<?xml version=”1.0” encoding=”utf-8”?>

<coffees>

 <region name=”Latin America”>

  <coffee>

    <taste>Curiously Mild And Bland</taste>

    <price>11.99</price>

    <availability>Year-round</availability>

    <bestwith>Breakfast</bestwith>

  <discountprice>9.59</discountprice>

  </coffee>

  <coffee>

    <taste>Exotic and Untamed</taste>

    <price>12.99</price>

    <availability>Year-round</availability>

    <bestwith>Dessert</bestwith>

  <discountprice>10.39</discountprice>

 </coffee>

 </region>

 <region name=”Africa”>

  <coffee>

    <taste>Exotic and Untamed</taste>

    <price>14.99</price>

    <availability>Limited</availability>

    <bestwith>Chocolate</bestwith>

    <discountprice>11.99</discountprice>

  </coffee>

  <coffee>

    <taste>Solid Yet Understated</taste>

    <price>9.99</price>

    <availability>Year-round</availability>

    <bestwith>Elephant Ears</bestwith>

    <discountprice>7.99</discountprice>

  </coffee>

 </region>

</coffees>

Renaming an Element

Renaming an element is another technique that involves the use of literal text and xsl:value-of combination. Suppose, for example, that I want to rename the taste element to description in the coffee-light.xml. Within the stylesheet, I can create a single template rule to perform this operation:

 <!-- coffee-rename.xsl -->

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

  <!-- Rename taste to description -->

  <xsl:template match=”taste”>

    <description><xsl:value-of select=”.”/></description>

  </xsl:template>

  

</xsl:stylesheet>

The template rule uses a match pattern of taste to return all taste elements. The xsl:value-of instruction, with its select value of ., converts the content of each taste element to a string. The result is the following document:

<?xml version=”1.0” encoding=”utf-8”?>

<coffee name=”Guatemalan Express” origin=”Guatemala”>

  <description>Curiously Mild And Bland</description>

  <price>11.99</price>

  <availability>Year-round</availability>

  <bestwith>Breakfast</bestwith>

</coffee>

Removing an Element

Because XSLT’s built-in templates automatically add the content of elements, you frequently have occasions when you need to prevent an element from appearing in the result document. XSLT doesn’t have a remove instruction, but you can ensure an element doesn’t appear in the transformation by using one of two methods.

Explicitly removing an element

The first technique is to explicitly delete an element by defining an empty template rule and using the element as the rule’s match pattern. An empty template rule is one that has a match pattern defined but no accompanying template. For example:

<xsl:template match=”dog”/>

An empty template rule tells the processor to grab a node set and deliberately do nothing with it. As an illustration, I produce a result document that removes the taste and bestwith elements as part of the coffee element. After I set up my stylesheet, it looks like the following:

<!-- coffee-remove.xsl -->

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

  <!-- Remove taste element -->

  <xsl:template match=”taste”/>

  <xsl:template match=”bestwith”/>

  <!-- Copy everything else over -->

  <xsl:template match=”@*|node()”>

    <xsl:copy>

      <xsl:apply-templates select=”@*|node()”/>

    </xsl:copy>

  </xsl:template>

</xsl:stylesheet>

By defining an empty template rule for the taste and bestwith elements, I remove these as part of the result. I can then use my “catch-all” template rule to simply copy everything else over. The XML code generated from the transformation looks like:

<?xml version=”1.0” encoding=”utf-8”?>

<coffees>

 <region name=”Latin America”>

  <coffee name=”Guatemalan Express” origin=”Guatemala”>

    <price>11.99</price>

    <availability>Year-round</availability>

  </coffee>

  <coffee name=”Costa Rican Deacon” origin=”Costa Rica”>

    <price>12.99</price>

    <availability>Year-round</availability>

  </coffee>

 </region>

 <region name=”Africa”>

  <coffee name=”Ethiopian Sunset Supremo” origin=”Ethiopia”>

    <price>14.99</price>

    <availability>Limited</availability>

  </coffee>

  <coffee name=”Kenyan Elephantismo” origin=”Kenya”>

    <price>9.99</price>

    <availability>Year-round</availability>

  </coffee>

  </region>

</coffees>

Remember

Because the taste and bestwith elements don’t contain any other elements, running empty template rules on them only removed these two elements and nothing else. However, keep in mind, if you create an empty template rule for an element with children, the empty rule removes the specified element along with its children during the transformation. For example, running <xsl:template match=”coffees”/> on the coffee.xml file generates an empty document, because coffees contains all other elements.

Implicitly removing an element

You can also implicitly remove an element by essentially ignoring it when template rules are applied. To demonstrate, imagine that you want to generate a new selectcoffees structure that removes the region, taste, and bandwidth elements. Here’s the XSLT code that you use to do this operation:

<!-- coffee-remove2.xsl -->

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

  <xsl:template match=”/”>

    <selectcoffees>

      <xsl:apply-templates/>

    </selectcoffees>

  </xsl:template>

  <!-- Copy coffee and its price and availability children -->

  <xsl:template match=”coffee”>

    <coffee>

      <price><xsl:apply-templates select=”price”/></price>

      <availability><xsl:apply-templates select=”availability”/></availability>

    </coffee>

  </xsl:template>

  

</xsl:stylesheet>

A new document element named selectcoffees is created in the first template. The start and end tags of selectcoffees is plugged in before and after the xsl:apply-templates instruction, which is run on all descendants of the root node (the match pattern).

The coffee template rule reconstructs the coffee element, but it does so by using literal text to redefine the price and availability tags and by using xsl:apply-templates to generate the child element’s content. Notice that the context for these xsl:apply-templates instructions is very specific because the select attribute is defined for them, applying just the price and availability elements. As a result, taste and bestwith elements are summarily dropped from the result document. (Boy, XSLT is a tough business!)

I also want to get rid of the region element, but because it doesn’t have any text nodes defined as content, I simply allowed the built-in template rule to do its thing; in doing so, the region element tags aren’t added to the result document. The end result is a new selectcoffees structure:

<?xml version=”1.0” encoding=”utf-8”?>

<selectcoffees>

  <coffee>

    <price>11.99</price>

    <availability>Year-round</availability>

  </coffee>

  <coffee>

    <price>12.99</price>

    <availability>Year-round</availability>

  </coffee>

  <coffee>

    <price>14.99</price>

    <availability>Limited</availability>

  </coffee>

  <coffee>

    <price>9.99</price>

    <availability>Year-round</availability>

  </coffee>

</selectcoffees>

Removing an Attribute

So far in this chapter, I’ve been manipulating elements to generate the result documents, but you can similarly work with attributes as well. (If you recall, attributes are name/value pairs that describe the element it is associated with.)

Just as I showed you in the preceding section how to remove an element, an attribute can also be omitted from the result document in a variety of ways. One common technique is to reconstruct the element in a template rule, leaving behind any attributes you don’t want to include. In the following stylesheet, I create a new derivative of coffee-light.xml that omits the origin attribute from the coffee  element:

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

  <!-- Copy coffee but don’t include the origin attribute -->

  <xsl:template match=”coffee”>

    <coffee name=”{@name}”>

      <xsl:apply-templates/>

    </coffee>

  </xsl:template>

  <!-- Copy everything else over -->

  <xsl:template match=”@*|node()”>

    <xsl:copy>

      <xsl:apply-templates select=”@*|node()”/>

    </xsl:copy>

  </xsl:template>

</xsl:stylesheet>

The result is shown here:

<?xml version=”1.0” encoding=”utf-8”?>

<coffee name=”Guatemalan Express”>

  <taste>Mild and Bland</taste>

  <price>11.99</price>

  <availability>Year-round</availability>

  <bestwith>Breakfast</bestwith>

</coffee>

A second example that follows highlights another technique to remove attributes. Suppose I want to remove the attributes from the region element. I can do this with the following XSLT code:

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

  <!-- Copy region, but not its attributes -->

  <xsl:template match=”region”>

    <xsl:copy>

     <xsl:apply-templates select=”*|node()”/>

    </xsl:copy>

  </xsl:template>

  <!-- Copy everything else over -->

  <xsl:template match=”@*|node()”>

    <xsl:copy>

      <xsl:apply-templates select=”@*|node()”/>

    </xsl:copy>

  </xsl:template>

</xsl:stylesheet>

Looking closer at the stylesheet, the first template copies the region tags to the result document with xsl:copy. It also uses xsl:apply-templates to add its content to the output as well. However, the select attribute value of xsl:apply-templates selects elements and other nodes, but it specifically does not include attributes. The attribute-free result for region is shown here:

<?xml version=”1.0” encoding=”utf-8”?><coffees>

 <region>

  <coffee name=”Guatemalan Express” origin=”Guatemala”>

    <taste>Mild and Bland</taste>

    <price>11.99</price>

    <availability>Year-round</availability>

    <bestwith>Breakfast</bestwith>

  </coffee>

  <coffee name=”Costa Rican Deacon” origin=”Costa Rica”>

    <taste>Solid Yet Understated</taste>

    <price>12.99</price>

    <availability>Year-round</availability>

    <bestwith>Dessert</bestwith>

  </coffee>

 </region>

 <region>

  <coffee name=”Ethiopian Sunset Supremo” origin=”Ethiopia”>

    <taste>Exotic and Untamed</taste>

    <price>14.99</price>

    <availability>Limited</availability>

    <bestwith>Chocolate</bestwith>

  </coffee>

  <coffee name=”Kenyan Elephantismo” origin=”Kenya”>

    <taste>Thick And Chewy</taste>

    <price>9.99</price>

    <availability>Year-round</availability>

    <bestwith>ElephantEars</bestwith>

  </coffee>

  </region>

</coffees>

Reordering Elements

You can rearrange elements in the result document by recreating the element in the image of your choosing. For example, I can shuffle the children of the coffee element in the coffee-light.xml file by coding the following XSLT:

<!-- coffee-reorder.xsl -->

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

  <!-- Reorder elements -->

  <xsl:template match=”coffee”>

    <coffee>

      <availability><xsl:apply-templates select=”availability”/></availability>

      <price><xsl:apply-templates select=”price”/></price>

      <bestwith><xsl:apply-templates select=”bestwith”/></bestwith>

      <taste><xsl:apply-templates select=”taste”/></taste>

    </coffee>

  </xsl:template>

</xsl:stylesheet>

By using a combination of literal text and xsl:apply-templates instructions, I recreate each child within a new sequential order. The result is shown here:

  <coffee>

    <availability>Year-round</availability>

    <price>11.99</price>

    <bestwith>Breakfast</bestwith>

    <taste>Curiously Mild And Bland</taste>

  </coffee>

Tip

Not all reordering needs to be manually done. You can also automatically sort elements using the xsl:sort instruction. I discuss sorting in Chapter 9.

Merging Elements

You can combine elements in various fashions to form new elements. For instance, suppose I want to create a new tagline element for the coffee element in coffee-light.xml. I want to use this new element as a one-line advertisement to market the coffee. With the taste and bestwith elements, I already have bits of marketing information that can be used in the tagline. By combining these elements and adding additional text, I can produce a dynamically generated tagline for each coffee in a flash — and do so without any help whatsoever from an ad agency. The stylesheet to do this task is as follows:

<!-- coffee-merge.xsl -->

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

  <!-- Merge elements to create tagline element -->

  <xsl:template match=”coffee”>

    <coffee>

      <tagline>This Coffee Is <xsl:value-of select=”taste”/> And Best Enjoyed With <xsl:value-of select=”bestwith”/></tagline>

      <taste><xsl:value-of select=”taste”/></taste>

      <price><xsl:value-of select=”price”/></price>

      <availability><xsl:value-of select=”availability”/></availability>

      <bestwith><xsl:value-of select=”taste”/></bestwith>

    </coffee>

  </xsl:template>

</xsl:stylesheet>

As you can see, the tagline element is created using literal text and xsl:value-of instructions for both the taste and bestwith elements. The result is:

<?xml version=”1.0” encoding=”utf-8”?>

<coffee>

  <tagline>This Coffee Is Curiously Mild And Bland And Best Enjoyed With Breakfast</tagline>

  <taste>Curiously Mild And Bland</taste>

  <price>11.99</price>

  <availability>Year-round</availability>

  <bestwith>Curiously Mild And Bland</bestwith>

</coffee>

Adding Attributes

Like adding elements to your result document, you can add attributes in one of two ways. You can create attributes both through literal text as well as the xsl:attribute instruction.

Using literal text

Using the literal text method, I can add a currency attribute to the price  element of the coffee-light.xml document by using the following stylesheet:

<!-- coffee-addattribute_literal.xsl -->

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

  <!-- Add currency attribute -->

  <xsl:template match=”coffee”>

    <coffee>

      <taste><xsl:value-of select=”taste”/></taste>

      <price currency=”$US”><xsl:value-of select=”price”/></price>

      <availability><xsl:value-of select=”availability”/></availability>

      <bestwith><xsl:value-of select=”taste”/></bestwith>

    </coffee>

  </xsl:template>

</xsl:stylesheet>

The XML produced from the transformation is as follows:

<?xml version=”1.0” encoding=”utf-8”?>

<coffee>

  <taste>Curiously Mild And Bland</taste>

  <price currency=”$US”>11.99</price>

  <availability>Year-round</availability>

  <bestwith>Curiously Mild And Bland</bestwith>

</coffee>

Using xsl:attribute

The xsl:attribute can also be used to create an attribute. In the following example, I am creating a new salesevent element as a child of the coffee element, which has quarter, usregion, and supplier attributes:

<!-- coffee-addattribute_inst.xsl -->

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

  <!-- Add attribute using xsl:attribute instruction -->

  <xsl:template match=”coffee”>

    <coffee>

      <taste><xsl:value-of select=”taste”/></taste>

      <price currency=”$US”><xsl:value-of select=”price”/></price>

      <availability><xsl:value-of select=”availability”/></availability>

      <bestwith><xsl:value-of select=”taste”/></bestwith>

      <xsl:element name=”salesevent”>

        <xsl:attribute name=”quarter”>Q3</xsl:attribute>

        <xsl:attribute name=”usregion”>New England</xsl:attribute>

        <xsl:attribute name=”supplier”>Horacio Zeeman</xsl:attribute>

      </xsl:element>

    </coffee>

  </xsl:template>

</xsl:stylesheet>

The result is shown here:

<?xml version=”1.0” encoding=”utf-8”?>

<coffee>

  <taste>Curiously Mild And Bland</taste>

  <price currency=”$US”>11.99</price>

  <availability>Year-round</availability>

  <bestwith>Curiously Mild And Bland</bestwith>

  <salesevent quarter=”Q3” usregion=”New England” supplier=”Horacio Zeeman”/>

</coffee>

Moving an Attribute

An attribute describes something about the element in which it is contained and is treated as a child attribute node by the XSLT processor. On occasion, you may find it necessary to move an attribute from one element to another. To demonstrate, suppose I want to get rid of the region element in the coffee.xml document and replace it with a new region attribute in the coffees element. To do this, the value of the region element’s name attribute needs to become the value of the region attribute by using the following stylesheet:

<!-- coffee-move_attr.xsl -->

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

  <!-- Move region name to be an attribute of coffees -->

  <xsl:template match=”coffees”>

    <coffees region=”{region/@name}”>

      <xsl:apply-templates/>

    </coffees>

  </xsl:template>

  

  <!-- Copy coffee elements as is -->

  <xsl:template match=”coffee”>

    <xsl:copy-of select=”.”/>

  </xsl:template>

  

</xsl:stylesheet> 

The coffees template rule recreates the coffees element, and uses the attribute value template {region/@name} to provide the value for the new region attribute. The coffee elements are simply copied over using the second template rule. These instructions produce output as shown here:

<?xml version=”1.0” encoding=”utf-8”?>

<coffees region=”Latin America”>

  <coffee name=”Guatemalan Express” origin=”Guatemala”>

    <taste>Curiously Mild And Bland</taste>

    <price>11.99</price>

    <availability>Year-round</availability>

    <bestwith>Breakfast</bestwith>

  </coffee>

  <coffee name=”Costa Rican Deacon” origin=”Costa Rica”>

    <taste>Solid Yet Understated</taste>

    <price>12.99</price>

    <availability>Year-round</availability>

    <bestwith>Dessert</bestwith>

  </coffee>

  <coffee name=”Ethiopian Sunset Supremo” origin=”Ethiopia”>

    <taste>Exotic and Untamed</taste>

    <price>14.99</price>

    <availability>Limited</availability>

    <bestwith>Chocolate</bestwith>

  </coffee>

  <coffee name=”Kenyan Elephantismo” origin=”Kenya”>

    <taste>Thick And Chewy</taste>

    <price>9.99</price>

    <availability>Year-round</availability>

    <bestwith>Elephant Ears</bestwith>

  </coffee>

</coffees>

Converting Elements into Attributes

The difference between what constitutes an attribute and what constitutes a child element is often arbitrary, because both describe its parent in some way. In the coffee.xml file, for example, I defined a coffee’s name as an attribute of the coffee element, while its price is a child element. However, it could have just as easily been the reverse: name as a child element and price as an attribute. For my purposes here, I’m not concerned about which technique is best, but the important thing for XSLT is that moving from element to attribute and from attribute to element can be a typical scenario when transforming documents.

In the previous example, I move attributes from one element to another, but suppose I also want to tweak the structure of the coffee element too. In particular, I want to convert the price element into an attribute of coffee and add a new descriptions element that converts the taste and bestwith elements into attributes that describe coffee. I use the XSLT code shown here to do this task:

<!-- coffee-convert_to_attr.xsl -->

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

  <!-- Move region name to be an attribute of coffees -->

  <xsl:template match=”coffees”>

    <coffees region=”{region/@name}”>

      <xsl:apply-templates/>

    </coffees>

  </xsl:template>

  

  <xsl:template match=”coffee”>

    <coffee name=”{@name}” origin=”{@origin}” price=”{price}”>

      <availability><xsl:value-of select=”availability”/></availability>

      <descriptions taste=”{taste}” bestwith=”{bestwith}”/>

    </coffee>

  </xsl:template>

  

</xsl:stylesheet>

Although the first template rule moves the region name to be the coffees region attribute, the second template is what I’d like to focus on. It reconstructs the coffee element attribute by using literal text and attribute value templates. The first two — name=”{@name}” origin=”{@origin}” — simply re-create the attributes that the coffee element already had. An attribute value template that returns the value of the price element creates the new price attribute. The descriptions element follows suit with the taste and bestwith attributes. The results look like this:

<?xml version=”1.0” encoding=”utf-8”?>

<coffees region=”Latin America”>

  <coffee name=”Guatemalan Express” origin=”Guatemala” price=”11.99”>

    <availability>Year-round</availability>

    <descriptions taste=”Curiously Mild And Bland” bestwith=”Breakfast”/>

  </coffee>

  <coffee name=”Costa Rican Deacon” origin=”Costa Rica” price=”12.99”>

    <availability>Year-round</availability>

    <descriptions taste=”Solid Yet Understated” bestwith=”Dessert”/>

  </coffee>

  <coffee name=”Ethiopian Sunset Supremo” origin=”Ethiopia” price=”14.99”>

    <availability>Limited</availability>

    <descriptions taste=”Exotic and Untamed” bestwith=”Chocolate”/>

  </coffee>

  <coffee name=”Kenyan Elephantismo” origin=”Kenya” price=”9.99”>

    <availability>Year-round</availability>

    <descriptions taste=”Thick And Chewy” bestwith=”Elephant Ears”/>

  </coffee>

</coffees>

Remember

In this example, the built-in template for the price element is not called, because I didn’t use an xsl:apply-templates instruction in the coffee template rule. However, in cases in which the price element is applied, I need to define an empty template rule to prevent the price element’s content from appearing both as an attribute and as an element.

Converting Attributes into Elements

You can also perform the opposite action and convert attributes into elements. As a demonstration of this technique as well as others I’ve shown in this chapter, I am going to create a new XML structure based on coffee.xml, but with several changes:

bullet Convert the region element’s name attribute into a name child element.

bullet Add a new source element under coffee that gets its value based on the region element’s name and the coffee element’s origin attribute.

bullet Move the content of the price element to be the value of a new retail attribute and add a new wholesale attribute to price that is 60 percent of the retail value.

The following stylesheet sets up these conversions:

<!-- coffee-convert_to_elem.xsl.xsl -->

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

  <!-- Move region name to be a child element of region -->

  <xsl:template match=”region”>

    <region>

      <name><xsl:value-of select=”@name”/></name>

      <xsl:apply-templates/>

    </region>

  </xsl:template>

  

  <!-- Convert coffee element -->

  <xsl:template match=”coffee”>

    <coffee>

      <price retail=”{price}” wholesale=”{format-number( price*.6, ‘##.##’ )}”></price>

      <source><xsl:value-of select=”../@name”/>|<xsl:value-of select=”@origin”/></source>

      <availability><xsl:value-of select=”availability”/></availability>

      <xsl:apply-templates/>

    </coffee>

  </xsl:template>

  <!-- Remove elements -->

  <xsl:template match=”taste”/>

  <xsl:template match=”price”/>

  <xsl:template match=”availability”/>

  <xsl:template match=”bestwith”/>

</xsl:stylesheet>

When this stylesheet is run against the coffee.xml file, the results from the transformation are as follows:

<?xml version=”1.0” encoding=”utf-8”?>

 <region>

   <name>Latin America</name>

   <coffee>

     <price retail=”11.99” wholesale=”7.19”/>

     <source>Latin America|Guatemala</source>

     <availability>Year-round</availability>

  </coffee>

  <coffee>

    <price retail=”12.99” wholesale=”7.79”/>

    <source>Latin America|Costa Rica</source>

    <availability>Year-round</availability>

  </coffee>

 </region>

 

 <region>

  <name>Africa</name>

  <coffee>

    <price retail=”14.99” wholesale=”8.99”/>

    <source>Africa|Ethiopia</source>

    <availability>Limited</availability>

  </coffee>

  <coffee>

    <price retail=”9.99” wholesale=”5.99”/>

      <source>Africa|Kenya</source>

      <availability>Year-round</availability>

  </coffee>

 </region>

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

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