WEEK 1 Day 6
Conditional and Iterative Processing

Yesterday you learned how to insert text, elements, and attributes into the resulting output. You also learned how to copy elements from the source document into the resulting output. These skills are important when you’re transforming XML because you now can create nearly all sorts of output from an element or attribute.

Also key to the transformation process is the flow of control. Currently, you can influence it—and thus what the output looks like—through the use of templates. Although templates are powerful, they are not always the best tool when you need to differentiate between nodes to be inserted.

The focus of today’s lesson is XSLT functionality that enables you to selectively act on elements within an invoked template.

Today you will learn the following:

• How to iterate through a node-set

• How to act on nodes based on the data value of those nodes

• How to create conditional expressions

• More details about filtering nodes based on predicate expressions

Iterating Through a Node-Set

NEW TERM

So far, the only way to control the flow of control has been to use templates. Templates are powerful and flexible but also relatively hard to work with because processing is driven by the source document’s data and the flow of control is therefore hard to predict. This is the nature of push processing, in which something outside the program determines the next action to be taken. In effect, the program reacts to some event.

NEW TERM

Push processing is mainly responsible for the power of XSLT, but in some cases, it is hard to use and read. Much easier to use and much easier to follow when it comes to flow of control is pull processing, in which a program retrieves data and acts on it. This means that the program determines the next action to be taken. This process is called pull processing because the program “pulls in the data.”

A perfect example of push processing occurs when templates are matched. When you output a value using the xsl:value-of element, you use a process that is more or less pull processing, especially if you select a value from an element or attribute using absolute addressing. Although this form of pull processing is quite handy, it isn’t useful when you want to select a node-set and do something with it. The xsl:for-each element comes into play here. The xsl:for-each element enables you to iterate through a node-set and act on the nodes in the node-set separately. This element is much like a template, but it uses pull processing instead of push processing.

Processing Each Node in a Node-Set

Most programming languages, such as C, Java, and Visual Basic, contain a statement that can be used to iterate or loop a predetermined number of times. This statement is called a for loop; in C and Java, it looks like this:

for (var i = 1; i <= 10;i++) {
    //execute some code
}

The code is based on a counter denoted by the variable i. It starts off with the value 1, and each time the code in between the curly braces is executed, its value is increased by 1. This process goes on as long as the value is 10 or lower. In Visual Basic (or VBScript), the same code would look like this:

For i = 1 To 10
    'execute some code
Next

The Visual Basic code is somewhat easier to read if you’re not familiar with programming, yet it does exactly the same thing. The Next statement serves the same purpose as the ending curly brace in C or Java: It denotes the end of the code block to be executed with each iteration.

The problem with these constructs is that they iterate a predetermined number of times, and such code is not very handy when you’re using collections or sets of data. In that case, you just want to iterate through each item in the collection, no questions asked, without having to determine the number of items in the collection. In Visual Basic, this problem has been solved by adding another type of for loop:

For Each x In MyCollection
    'execute some code with x
Next

The For Each ... In ... statement is almost declarative programming because it tells the program to do something with each item in the collection; how each item is selected is up to the program. You can use this approach because you are working with a collection, which by its very nature doesn’t have any particular order to the items it contains. This also goes for node-sets because they are processed as the processor encounters them.

Note

Manipulating the order in which nodes are processed will be discussed on Day 12, “Sorting and Numbering.”

XSLT has a construct that is similar (if not the same) as Visual Basic’s For Each ... In ... statement: the xsl:for-each element. Just like a match template, this element has a select attribute with which you select the elements that need to be processed using an XPath expression. The best way to show you how this element works is to use an example. The sample XML source in Listing 6.1 is based on an earlier sample.

LISTING 6.1 Sample XML Document

     <?xml version=″“1.0”" encoding="UTF-8"?>
     <employees>
       <employee name="Joe Dishwasher">
         <phonenumber type="home">555-1234</phonenumber>
         <phonenumber type="fax">555-2345</phonenumber>
         <phonenumber type="mobile">555-3456</phonenumber>
       </employee>
       <employee name="Carol Waitress">
         <phonenumber type="home">555-5432</phonenumber>
         <phonenumber type="mobile">555-9876</phonenumber>
       </employee>
     </employees>

Note

You can download the sample listings in this lesson from the publisher’s Web site.

So far, all transformations have been performed based on templates. Listing 6.2 also creates output from Listing 6.1, but does so by using the xsl:for-each element instead of a template-based approach with matching.

LISTING 6.2 Stylesheet for Listing 6.1 Using xsl:for-each

     1:  <?xml version=″“1.0”" encoding="UTF-8"?>
     2:  <xsl:stylesheet version=″“1.0”"
     3:    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     4:
     5:    <xsl:template match="/">
     6:      <xsl:for-each select="employees/employee">
     7:        *<xsl:value-of select="@name" />*
     8:        <xsl:for-each select="phonenumber">
     9:          -<xsl:value-of select="@type" />: <xsl:value-of select="." />
     10:       </xsl:for-each>
     11:     </xsl:for-each>
     12:   </xsl:template>
     13: </xsl:stylesheet>

ANALYSIS

Listing 6.2 has only one template, which matches the document root. Instead of using xsl:apply-templates, the template contains xsl:for-each elements on lines 6 and 8 to dive deeper into the source’s XML document tree. The xsl:for-each element’s select expression on line 6 selects all the employee elements that are children of the employees element. This is a major difference with templates. A template matching the employee element will match any employee element, no matter where in the document it is. The xsl:for-each element selects only elements relative to the context node, unless instructed otherwise—for instance, using //employee, which selects all the employee elements in the entire source document.

With each employee node in the processed node-set, line 7 inserts the name of the employee. The xsl:for-each element on line 8 iterates through all the phonenumber elements that are children of the current employee and outputs the type attribute and value. Listing 6.3 shows the result from applying Listing 6.2 to Listing 6.1.

LISTING 6.3 Result from Applying Listing 6.2 to Listing 6.1

OUTPUT

      <?xml version=″“1.0”" encoding="utf-8"?>
            *Joe Dishwasher*

              -home: 555-1234
              -fax: 555-2345
              -mobile: 555-3456
            *Carol Waitress*

              -home: 555-5432
              -mobile: 555-9876

Note

The * and - inserted by the stylesheet in Listing 6.3 make the output easier to read. These characters serve no other function.

In Listing 6.2, you can easily determine what the context node is before you even run the code. With each xsl:for-each element, you dig deeper into the document tree, iterating through child elements of the element that is currently iterated by a higher level xsl:for-each element. Because you specifically select the nodes to be processed, there are no side effects, something that is common with templates.

Caution

The xsl:for-each element cannot exist outside a template and therefore cannot serve as a replacement for a template. Of the elements discussed so far, only the xsl:template element can be used as a child element of xsl:stylesheet. You need to place the other elements inside a template. Also, remember that you cannot nest templates.

Listing 6.2 might make you think that you can use only one template if you’re using xsl:for-each. That is not the case. You can mix the push processing of templates with the pull processing of xsl:for-each. The stylesheet in Listing 6.4 employs two templates and an xsl:for-each element.

LISTING 6.4 Stylesheet with Multiple Templates and Iteration

     1:  <?xml version=″“1.0”" encoding="UTF-8"?>
     2:  <xsl:stylesheet version=″“1.0”"
     3:    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     4:
     5:    <xsl:template match="/">
     6:      <xsl:for-each select="employees/employee">
     7:        *<xsl:value-of select="@name" />*
     8:        <xsl:apply-templates />
     9:     </xsl:for-each>
     10:   </xsl:template>
     11:
     12:   <xsl:template match="phonenumber">
     13:     -<xsl:value-of select="@type" />: <xsl:value-of select="." />
     14:   </xsl:template>
     15: </xsl:stylesheet>

ANALYSIS

Applying Listing 6.4 to Listing 6.1 produces nearly the same result as Listing 6.2. The only difference is the whitespace that is inserted because of the indentation of the stylesheet elements. Yet, the two stylesheets are rather different. Instead of using xsl:for-each to iterate through the phonenumber elements as on line 8 of Listing 6.2, Listing 6.4 uses a match template on line 12, in the process transferring from pull processing to push processing.

Filtering Node-Sets

In most stylesheets used in this and previous lessons, either all nodes in a node-set were processed, or none of them were. You have learned only how to manipulate them using position predicates that tell the processor which node it should get from the source XML, based on its position within the document. More often, you will want to operate on certain nodes, based on their name or value, and leave the other nodes. You can do so by using predicates that go beyond position filtering. These predicates can be used to compare the value of some node to the value you need it to be. Also, you can compare node names, check whether certain nodes exist, and so on.

NEW TERM

When you use predicates to filter nodes in a node-set, the expression used in the predicates has to be evaluated and result in the values true or false. If the value is true, the node being evaluated will be processed; if the value is false, the node will be ignored. The values true and false are said to be Boolean values. Values of the Boolean data type can be only true or false, 1 or 0, on or off. In the case of XSLT, the values are true or false.

An expression’s resulting value isn’t always a Boolean value. It also can be a number, string, node-set, or tree fragment. In those cases, the expression’s resulting value is converted to Boolean. This topic will be covered later; for now, a simple example will help you understand predicate expressions. In Listing 6.5, a predicate is used with a comparison between the value of the type attribute and literal value ‘home’.

LISTING 6.5 Iteration Using a Filter Expression

     1:  <?xml version=″“1.0”" encoding="UTF-8"?>
     2:  <xsl:stylesheet version=″“1.0”"
     3:    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     4:
     5:    <xsl:template match="/">
     6:      <xsl:for-each select="employees/employee">
     7:        *<xsl:value-of select="@name" />*
     8:        <xsl:for-each select="phonenumber[@type='home']">
     9:          <xsl:value-of select="@type" />: <xsl:value-of select="." />
     10:       </xsl:for-each>
     11:     </xsl:for-each>
     12:   </xsl:template>
     13: </xsl:stylesheet>

ANALYSIS

On line 8 in Listing 6.5, a predicate filters the phonenumber elements. If the value of the type attribute is ‘home’, the node is processed and thus written into the resulting output. Hence, the listing will output only the employees’ home phone numbers and ignore any other type of phone number, as you can see in Listing 6.6.

OUTPUT

LISTING 6.6 Result from Applying Listing 6.5 to Listing 6.1

      <?xml version=″“1.0”" encoding="utf-8"?>
         *Joe Dishwasher*
         home: 555-1234
         *Carol Waitress*
         home: 555-5432

ANALYSIS

In Listing 6.6, no side effects occur from filtering the node-set. The phonenumber elements that don’t satisfy the condition of the predicate are ignored completely.

If you were to use a template to match the phonenumber elements rather than select them, you would see the difference between matching and selecting. In Listing 6.7, thexsl:for-each element is replaced by a template.

Listing6.7 Template Using a Filter Expression

     1:  <?xml version=″“1.0”" encoding="UTF-8"?>
     2:  <xsl:stylesheet version=″“1.0”"
     3:    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     4:
     5:    <xsl:template match="/">
     6:      <xsl:for-each select="employees/employee">
     7:        *<xsl:value-of select="@name" />*
     8:        <xsl:apply-templates select="*" />
     9:      </xsl:for-each>
     10:   </xsl:template>
     11:
     12:   <xsl:template match="phonenumber[@type='home']">
     13:     <xsl:value-of select="@type" />: <xsl:value-of select="." />
     14:   </xsl:template>
     15: </xsl:stylesheet>

ANALYSIS

Listing 6.7 uses a template on line 12 instead if the xsl:for-each element on line 8 of Listing 6.5. Both are meant to operate on a specific phonenumber element. You might think that Listing 6.7 will yield the same result as Listing 6.5. There is a catch, however, because the elements are matched rather than selected. The actual result is shown in Listing 6.8.

OUTPUT

LISTING 6.8 Result from Applying Listing 6.7 to Listing 6.1

      1:  <?xml version=″“1.0”" encoding="utf-8"?>
      2:        *Joe Dishwasher*
      3:        home: 555-1234555-2345555-3456
      4:        *Carol Waitress*
      5:        home: 555-5432555-9876

ANALYSIS

If you look closely at lines 3 and 5, you can see that the phone numbers are not correct. They actually consist of the values of all the employees’ phone number elements. The home phone number is matched correctly by the template on line 12 of Listing 6.7 and has the correct output. The other phonenumber elements are not matched because the matching rule in the template applies only to home phone numbers. Therefore these elements are matched by the built-in template rule. This is why their value is written to be output. Because the elements are matched instead of selected, a side effect occurs for the nodes that aren’t matched. This is exactly what makes writing style sheets on the basis of matching difficult. You have to be very aware of any side effects that may occur and preemptively handle them.

Using Node-Set Functions

On Day 3, “Selecting Data,” you learned that you can use a predicate with the number of the node you want to select from a node-set. This number is the position that the node has within that node-set. This position is partially determined by the order of the nodes in the source document. Listing 6.9 should jog your memory if you don’t remember.

LISTING 6.9 Stylesheet Using a Numeric Predicate

     1:  <?xml version=″“1.0”" encoding="UTF-8"?>
     2:  <xsl:stylesheet version=″“1.0”"
     3:    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     4:
     5:    <xsl:template match="/">
     6:      <xsl:for-each select="employees/employee/phonenumber[2]">
     7:        *<xsl:value-of select="../@name" />*
     8:        <xsl:value-of select="@type" />: <xsl:value-of select="." />
     9:      </xsl:for-each>
     10:   </xsl:template>
     11: </xsl:stylesheet>

When you apply Listing 6.9 to Listing 6.1, you get the result in Listing 6.10.

OUTPUT

LISTING 6.10 Result from Applying Listing 6.9 to Listing 6.1

      <?xml version=″“1.0”" encoding="utf-8"?>
         *Joe Dishwasher*
         fax: 555-2345
         *Carol Waitress*
         mobile: 555-9876

ANALYSIS

In Listing 6.10, only the phonenumber elements that were second in their respective node-sets are shown. Line 6 in Listing 6.9 ensures that this number is shown, using the predicate expression [2]. But wait a minute. Shouldn’t the value of the predicate expression yield a Boolean value? It should indeed, and it does, although not explicitly. Because only a numeric value is available, XSLT assumes that you actually meant the following:

<xsl:for-each select="employees/employee/phonenumber[position ()=2]">

XSLT assumes that you want to compare the numeric value to the position () function. This function returns the numeric position of the context node within the current node-set. For the first node, the resulting expression is 1=2, which yields false. For the second node, the actual expression becomes 2=2, which is true; hence, the node is processed.

XSLT contains another function that has bearing on the current context: last (). It returns the numeric position of the last node within the current node-set. If you change line 6 in Listing 6.9 to

<xsl:for-each select="employees/employee/phonenumber[last ()]">

it will process only the last phonenumber element of each employee’s phonenumber node-set. Here again, XSLT assumes that last () actually means position ()=last () because the last () function returns a numeric value. Listing 6.11 shows the result if you exchange the preceding line for line 6 in Listing 6.9.

OUTPUT

LISTING 6.11 Result from Stylesheet Using last ()

      <?xml version=″“1.0”" encoding="utf-8"?>
         *Joe Dishwasher*
         mobile: 555-3456
         *Carol Waitress*
         mobile: 555-9876

ANALYSIS

Listing 6.11 shows the correct mobile number of both employees. For both employees, this number is the last phonenumber element in the node-set. In the case of Joe Dishwasher, it is the third element, and in the case of Carol Waitress, the second. So, the last () function returns 3 and 2, respectively, which are then compared to the position of the current node.

Conditional Processing

NEW TERM

You have learned how to filter out nodes that you don’t want, either through matching or by selection. You therefore can process those nodes you want and act on them as necessary to create the output you want. What you haven’t learned yet is how to insert data based on some condition. Filtering using predicates comes close, but many things you can do with conditional processing are very hard using predicate expressions and templates or iteration. In conditional processing, a piece of code is executed if some condition holds true.

Conditional processing comes in two forms. One allows you to execute a piece of code based on a condition. The other allows you to select a piece of code to execute from multiple alternatives.

Simple Conditional Processing

The easiest form of conditional processing uses the xsl:if element, which has one attribute named test. This attribute’s value needs to be an expression that yields a Boolean value. If the resulting value is true, the body of the element is executed; otherwise, it is not. Listing 6.12 shows the xsl:if element in action.

LISTING 6.12 Stylesheet Using xsl:if

     1:  <?xml version=″“1.0”" encoding="UTF-8"?>
     2:  <xsl:stylesheet version=″“1.0”"
     3:    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     4:
     5:    <xsl:template match="/">
     6:      employees:
     7:      <xsl:for-each select="employees/employee">
     8:        <xsl:value-of select="@name" />
     9:        <xsl:if test="position () != last ()">, </xsl:if>
     10:       <xsl:if test="position () = last ()">.</xsl:if>
     11:     </xsl:for-each>
     12:   </xsl:template>
     13: </xsl:stylesheet>

ANALYSIS

Line 7 of Listing 6.12 selects all employee elements in Listing 6.1 and iterates through them. On line 8, the value of the name attribute is sent to the output. Lines 9 and 10 both contain an xsl:if element to see what needs to be inserted next. Line 9 checks whether the current node is not the last node and, if that is true, inserts a comma. Line 10 checks whether the current node is the last node and, if so, inserts a period. The result is shown in Listing 6.13.

OUTPUT

Listing 6.13 Result from Applying Listing 6.12 to Listing 6.1

      <?xml version="1.0" encoding="utf-8"?>
          employees:
          Joe Dishwasher, Carol Waitress.

ANALYSIS

Listing 6.13 contains a simple list of the employees in Listing 6.1. The employees’ names are separated by commas, and the list ends with a period. No big deal right? Now try to imagine creating this list without using templates or xsl:for-each. You cannot insert a comma with each element; if you do, the list would end in a comma instead of a period. Also, you want to insert a period only after the last element. With templates, you would probably end up writing a separate template for the last element. The approach in Listing 6.12 is much easier, requiring two lines of code.

Now look closer at line 10. As I said, it checks whether the context node is the last node in the node-set. If it is, a period is inserted. It does so by comparing the value returned by the position () function with that returned by the last () function.

Caution

In match and select expressions with predicates, last () implies a position ()=last () comparison. With test expressions, this is not the case. The last () function returns a number that is converted to a Boolean value, with 0 being false and any other value being true.

Now look at line 9. Instead of comparing the position and the last position using =, this line uses !=. The former, of course, checks whether the values on the left and right sides are equal. The latter checks the opposite and returns true if the values left and right of it are not equal. Hence, the comma is inserted with each element that is not the last element.

You can use several operators when you need to compare two values. They are shown in Table 6.1.

TABLE 6.1 Comparison Operators

Image

The operators in Table 6.1 always need to appear in between the values you want to compare. For the last four operators, you need to read from left to right. An expression such as A &gt; B returns true if the value of A is greater than the value of B. These operators may look a little funny, but remember that XML uses certain special characters. Therefore, writing A > B would result in an error. To get around this problem, you can output escape the operators where necessary.

Line 9 in Listing 6.12 could have been written differently using another operator. For instance,

<xsl:if test="position () &lt; last ()">, </xsl:if>

yields the same result. The position () function’s result can, of course, never be greater than the last () function’s result, but if it could, the test expression given here would actually be more specific. The != operator just forces inequality, but it may be less than or greater than.

Listing 6.9 used a filter so that only the second phone number of each employee was shown. Now look at Listing 6.14, which uses xsl:if.

LISTING 6.14 Filtering Nodes with xsl:if

     1:  <?xml version=″“1.0”" encoding="UTF-8"?>
     2:  <xsl:stylesheet version=″“1.0”"
     3:    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     4:
     5:    <xsl:template match="/">
     6:      <xsl:for-each select="employees/employee/phonenumber">
     7:        <xsl:if test="position ()=2">
     8:          *<xsl:value-of select="../@name" />*
     9:          <xsl:value-of select="@type" />: <xsl:value-of select="." />
     10:       </xsl:if>
     11:     </xsl:for-each>
     12:   </xsl:template>
     13: </xsl:stylesheet>

ANALYSIS

When you apply Listing 6.14 to Listing 6.1, it does not yield the same result as Listing 6.9. In Listing 6.9, the filter expression that filters the node-sets sees the phone numbers of each employee as separate node-sets. Line 6 in Listing 6.14, however, makes them into one node-set. So, the current context of line 7 is a node-set of all employees’ phonenumber elements. It therefore tests positive on only one node. Its output is shown in Listing 6.15.

OUTPUT

LISTING 6.15 Result from Applying Listing 6.14 to Listing 6.1

<?xml version=″“1.0”" encoding="utf-8"?>
        *Joe Dishwasher*
        fax: 555-2345

ANALYSIS

You can achieve the result in Listing 6.15 if you modify the filter expression on line 6 in Listing 6.9. Changing it to read employees/employee[1]/ phonenumber[2] would work, but there is a big difference in the way everything is processed. In Listing 6.14, each phonenumber element is processed. Line 7 sees to it that no output is generated for it unless it is the second node. When you use the filter expression, only one node is processed.

In most processors, filtering performs better than using conditions because fewer nodes are actually processed. This does not mean, however, that conditional processing is not the preferred method of attack in some situations. Listing 6.12 is a perfect example in which conditional processing is preferred over filtering with templates or selection.

Conditional Processing with Multiple Options

The xsl:if element enables you to test whether a certain condition is satisfied, and if it is, to execute code. What happens if you have several blocks of code and only one needs to be executed, based on a given condition? In that case, you need to create a chain of xsl:if elements, each testing a condition. The catch is that if more than one of those elements has an expression that tests positive, the code within each element is executed. If you want all the code executed, this outcome is fine, but if only one of the code blocks needs to be executed, you have a problem.

In XSLT, you can use a structure of elements to resolve this problem. The main element is xsl:choose. This element has two possible child elements: xsl:when and xsl:otherwise. The former is similar to the xsl:if element in that it contains code to be executed if the expression in the test attribute returns true. The xsl:when element can occur more than once as a child of the xsl:choose element, but only one of them is executed. The first element for which the expression returns true is executed. Any others that also satisfy their condition are ignored. If no xsl:when elements are executed, the xsl:otherwise element comes into play. Any code that it contains is executed if none of the xsl:when elements are executed. Listing 6.16 shows this structure of elements in action.

LISTING 6.16 Stylesheet Using choose-when-otherwise

   1:  <?xml version=″“1.0”" encoding="UTF-8"?>
   2:  <xsl:stylesheet version=″“1.0”"
   3:    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   4:
   5:    <xsl:template match="/">
   6:      <xsl:for-each select="employees/employee">
   7:        *<xsl:value-of select="@name" />*
   8:          <xsl:for-each select="phonenumber">
   9:            <xsl:choose>
   10:             <xsl:when test="@type='fax'">
   11:               Fax:    <xsl:value-of select="." />
   12:              </xsl:when>
   13:              <xsl:when test="@type='mobile'">
   14:                Mobile: <xsl:value-of select="." />
   15:              </xsl:when>
   16:              <xsl:otherwise>
   17:               Phone:  <xsl:value-of select="." />
   18:             </xsl:otherwise>
   19:           </xsl:choose>
   20:       </xsl:for-each>
   22:     </xsl:for-each>
   23:   </xsl:template>
   24: </xsl:stylesheet>

ANALYSIS

In Listing 6.16, the xsl:choose element starting on line 9 has several xsl:when child elements and an xsl:otherwise child element. The xsl:when elements check to see whether the type attribute of the element being processed has a certain value. If it does, the code within the xsl:when element is executed. If both xsl:when elements do not satisfy the condition of their test attribute, the xsl:otherwise element on line 16 kicks in.

Note

In Listing 6.16, the xsl:choose element is a child element of an xsl:for-each element. Using the element this way is not mandatory; instead, it can be a child element of a template. In fact, of the elements covered so far, only the xsl:stylesheet and xsl:template elements have very strict rules. The other elements can be contained in another element. You therefore can have an xsl:choose element inside an xsl:for-each element, which in turn is a child of an xsl:if element.

Listing 6.17 shows the result from applying Listing 6.16 to Listing 6.1.

OUTPUT

LISTING 6.17 Result from Applying Listing 6.16 to Listing 6.1

1:  <?xml version=″“1.0”" encoding="utf-8"?>
2:        *Joe Dishwasher*
3:
4:                Phone:  555-1234
5:                Fax:     555-2345
6:                Mobile: 555-3456
7:        *Carol Waitress*
8:
9:                Phone:  555-5432
10:               Mobile: 555-9876

ANALYSIS

In earlier listings, the phone numbers were preceded by the value of each phonenumber element’s type attribute. In Listing 6.17, you can see that this has been changed to text added in the stylesheet. Added effort has been put into making the numbers line up neatly. This was all possible through the use of xsl:choose. Line 5 in Listing 6.17 is inserted by line 11 in Listing 6.16. Notice the whitespace between Fax: and the xsl:value-of element, which is copied as is to the output. When the type attribute’s value is neither fax nor mobile, the xsl:otherwise element on line 16 of Listing 6.16 is invoked. This element outputs the text Phone: and the phone number. If a phonenumber element with a type not currently used in Listing 6.1 were included in the input, that element would also be written as if it were a regular phone number. In Listing 6.18, that is not the case.

LISTING 6.18 xsl:choose Element Without xsl:otherwise

1:  <xsl:choose>
2:    <xsl:when test="@type='fax'">
3:      Fax:    <xsl:value-of select="." />
4:    </xsl:when>
5:     <xsl:when test="@type='mobile'">
6:       Mobile: <xsl:value-of select="." />
7:    </xsl:when>
8:    <xsl:when test="@type='home'">
9:      Phone:  <xsl:value-of select="." />
10:   </xsl:when>
11: </xsl:choose>

ANALYSIS

Listings 6.18 and 6.16 produce the same output for Listing 6.1; this output is shown in Listing 6.17. However, because the xsl:otherwise element of Listing 6.16 is removed and replaced by the xsl:when element on line 8, any phonenumber element that is not of the home, fax or mobile type will not be displayed at all. As I just explained, the xsl:otherwise element would have displayed those elements. So, as you can see, having an xsl:otherwise element is by no means necessary, but it is useful for dealing with cases that aren’t handled by any of the xsl:when elements. If you don’t need to do anything in such situations, just leave out the xsl:otherwise attribute.

More About Expressions

On Day 3, you learned how to use XPath to match or select a node or node-set. Without going into the details further, you learned about predicates that make a comparison to filter out nodes. At this point, it is useful to recap, formalize, and expand on your knowledge of expressions.

Formalizing Expressions

Expressions are somewhat circular in nature. They contain elements that themselves can contain expressions. Dissecting a sample expression therefore gives you the best view of what an expression is and how it is built up. Look at the following example:

/employees/employee[position ()=1]/child::phonenumber[attribute::type='home']

NEW TERM

This match or select expression can be used in a template or with an xsl:for-each element. It is called a location path because it points to specific nodes in the node tree. These nodes are in the specified location and also satisfy the conditions given by the location path. You can see that the location path uses absolute addressing, because it starts with /. The location path consists of several location steps, separated by /. Each location step consists of the following:

• An optional axis

• A node test

• Zero or more predicates

In the preceding example, child specifies the axis of the third location step. It is separate from the node test with the double colon. In the other two location steps, the axes are implied (also child). The axis is followed by the node test, which specifies the names of the nodes that actually match on the specified axis. For the separate location steps, they are, in turn, employees, employee, and phonenumber.

So far, this description is very straightforward. The real complexity comes from predicates, which you use to make a more precise match or selection. The expression defined within a predicate always has to return a Boolean value. If that value is true, the node is processed further; if false, it is ignored. In most cases, a predicate expression is a comparison of two values. The values are compared using the operators in Table 6.1. Comparisons can be made

1. Between the value of a node and a literal value

2. Between the value of a node and the value returned by a function

3. Between the value of a node and the value of another node

4. Between the value returned by a function and a literal value

5. Between the value returned by a function and the value returned by another function

position ()=1 is an example of the fourth option; attribute::type='home' is an example of the first option. Predicates can themselves contain entire location paths, including predicates, so the following location path is perfectly legal:

employee/phonenumber[@type=/employees/employee[1]/phonenumber[3]/@type]

This location path compares the value of the context element’s type attribute to that of the same attribute of the third phonenumber element that is a child element of the first employee element. Although this sample is rather strange, you will find plenty of situations in which this sort of comparison makes sense.

Note

You can download a tool called XPath Visualizer from http://www.topxml.com/xpathvisualizer/default.asp. This tool can help you build expressions.

Using Multiple Predicates

You are not limited to using just one expression. You can actually test more than one thing just by adding extra predicates. In that case, the node is processed only if all predicates return true. Such an expression would look like this:

phonenumber[position ()=1][@type='home']

The preceding expression matches only phonenumber elements that are in the first position of the node-set currently being evaluated and that have a type attribute with the value home. You can add as many attributes as you like.

Combining Expressions

Instead of adding multiple predicates, you also can combine expressions. The expression from the preceding section can also be written like this:

phonenumber[position ()=1 and @type='home']

The expressions are combined using the and operator. Only if both expressions are true does the entire expression return true. You also can create expressions that return true if only one of them is true. To do so, you use the or operator like this:

phonenumber[position ()=1 or @type='home']

The expressions you use in predicates can be used with xsl:if and xsl:when as well. Because you cannot add two predicates together in that case, you have to use the Boolean operator and to combine expressions. Listing 6.19 shows how to use xsl:choose with an xsl:when element with such a test expression.

LISTING 6.19 Expression Using the Boolean Operator or

1:  <xsl:choose>
2:    <xsl:when test="@type='fax'">
3:      Fax:   <xsl:value-of select="." />
4:    </xsl:when>
5:    <xsl:when test="@type='mobile' or @type='home'">
6:      Phone: <xsl:value-of select="." />
7:    </xsl:when>
8:  </xsl:choose>

The output from applying Listing 6.19 to Listing 6.1 is shown in Listing 6.20.

OUTPUT

LISTING 6.20 Result from Applying Listing 6.19 to Listing 6.1

      <?xml version=″“1.0”" encoding="utf-8"?>
            *Joe Dishwasher*

                    Phone: 555-1234
                    Fax:   555-2345
                    Phone: 555-3456
           *Carol Waitress*

                    Phone: 555-5432
                    Phone: 555-9876

ANALYSIS

In Listing 6.20, both the home and mobile phones are treated as regular phones. Line 5 in Listing 6.19 uses an expression combined using the or operator to accomplish this result. If either expression to the left or right of the operator is true (or both), the node is processed.

You can combine multiple expressions by using multiple or and and operators.

Caution

The and operator has precedence over the or operator. This means that in the expression A or B and C, either B and C need to be true or A needs to be true. You can manipulate the order of precedence by using parentheses. In the expression (A or B) and C, the part between the parentheses is evaluated first and then used to evaluate the rest of the expression.

Using Boolean Functions

You’ve learned that expressions evaluate to Boolean values. These values, true and false, aren’t available as literal values, but two corresponding functions represent these values: true () and false (). These functions aren’t useful in most expressions because an expression already returns a Boolean value. Testing against them is therefore useless. Only in complex expressions combining functions and Boolean operators can these functions serve a purpose. They can be handy during the development process if you want to force the stylesheet to process certain code, so you can see whether it produces the right output when the code inside is invoked. For instance, if you have an xsl:choose element and want to force the xsl:otherwise code, you can temporarily set all the test attributes of the xsl:when elements to <xsl:when test="false ()">. That way, the code inside the xsl:when elements is never invoked. Listing 6.21 shows such a sample.

LISTING 6.21 Using false () to Force Flow of Control

1:  <xsl:choose>
2:    <xsl:when test="false ()">
3:      Fax:    <xsl:value-of select="." />
4:    </xsl:when>
5:    <xsl:when test=" false ()">
6:      Mobile: <xsl:value-of select="." />
7:    </xsl:when>
8:    <xsl:otherwise>
9:      Phone:  <xsl:value-of select="." />
10:   </xsl:otherwise>
11: </xsl:choose>

Applying Listing 6.21 to Listing 6.1 yields the result in Listing 6.22.

OUTPUT

LISTING 6.22 Result from Applying Listing 6.21 to Listing 6.1

<?xml version=″“1.0”" encoding="utf-8"?>
     *Joe Dishwasher*

        Phone:  555-1234
        Phone:  555-2345
        Phone:  555-3456
     *Carol Waitress*

        Phone:  555-5432
        Phone:  555-9876

ANALYSIS

As you can see from the result in Listing 6.22, only the xsl:otherwise block on lines 8 through 10 in Listing 6.21 is executed. The xsl:when blocks are ignored because their test attribute contains the function false ().

Negating an Expression Result

You’ve learned that you can use different comparison operators so that you can do more than just test equality. If you want to test for inequality instead of equality, you can use the != comparison operator instead. Another method is to use the not () function, which returns the opposite Boolean value from your expression. So, not (A=B) yields the same result as A!=B. Because you can use all these different operators, you may not see the value of this function. Basically, almost any comparison you can create by using not (), you also can create by using other operators. The not () function becomes useful after you take into account implicit conversion to Boolean values.

Conversion to Boolean Values

An expression doesn’t necessarily return a Boolean value. An expression that doesn’t compare anything but selects a node or node-set can return a number, string, node-set, or tree fragment. Because a predicate expression or an expression used in a test attribute expects a Boolean value, it implicitly converts the value to a Boolean value. Table 6.2 shows the rules used to convert the value to a Boolean value.

TABLE 6.2 Rules Applied When Converting Values to Boolean Values

Image

In Table 6.2, the results for numbers and strings are probably clear to you. Node-sets and tree fragments are a little more difficult. When used as a test on Listing 6.1, the following expression would return false:

/employees/employee/kids

There are no kids elements, so this expression returns an empty node-set and thus false. The expression

/employees/employee/phonenumber

returns a nonempty node-set and thus true.

Tree fragments are even trickier. A tree fragment consisting of only empty nodes, possibly with attributes, returns false. So /employees would return false because no elements contain text. However, if you convert Listing 6.1 into a document with only elements, it returns true because at least one element contains text.

So much for implicit conversion, but what if you want to compare the Boolean values of two non-Boolean values? Because the implicit conversion takes place after the entire expression is evaluated, comparing these values is not possible without a little help. This help comes in the form of the boolean () function. The expression

boolean (employee[1]/phonenumber) and boolean (employee[2]/phonenumber)

implicates that both employees must have at least one phone number, or the whole deal is off. This is case because the boolean () function converts the node-sets passed as arguments (between the parentheses) to a Boolean value before combining the results of the separate functions with the and operator.

Summary

Today you learned that you can use xsl:for-each to select and iterate through a node-set. The advantage of using this approach over using templates is that no side effects occur because of nodes you didn’t handle. Using templates as much as possible is recommended. They are the heart and soul of XSLT, which is all about data-driven development. Most processors are therefore optimized for templates as opposed to using xsl:for-each.

You also learned that you can use xsl:if and xsl:choose to branch off your flow of control so that certain code is executed only when certain conditions are satisfied. By using all kinds of comparison operators, you can create very complex conditions. You also can join multiple expressions by using multiple predicates or the Boolean operator and. With its brother or, you can also create expressions in which either of two conditions may be satisfied to execute certain code.

Finally, you learned that when an expression does not return a Boolean value, the value is implicitly converted to a Boolean value. You can force this conversion by using the boolean () function.

Now that you have learned about all the basic elements and functions in XSLT, you’re ready to learn about creating different output types and manipulating whitespace behavior. That will be the subject of tomorrow’s lesson.

Q&A

Q Is there any way that expressions I create will return an error?

A No. If an expression works—that is, if its syntax is correct—it will never return an error. Whether it does what you want it to do is another matter. XSLT has been created in such a way that errors are possible only when the syntax is incorrect, so when your stylesheet works, it will continue to work, no matter how crazy the input.

Q All those implicit functions make it hard to see what’s going on. Why shouldn’t I write out everything?

A Getting used to implicit functions (and conversion) takes some time. If you want to be certain that the right thing happens, explicitly writing all code is good practice. You also can write code using the shortcuts and write explicit code only when something doesn’t work the way it should.

Q Can xsl:when exist outside an xsl:choose element?

A No. xsl:when may occur only within xsl:choose. xsl:choose also must contain at least one xsl:when element.

Workshop

This workshop tests whether you understand all the concepts you learned today. It is helpful to know and understand the answers before starting tomorrow’s lesson. You can find the answers to the quiz questions and exercises in Appendix A.

Quiz

1. True or False: xsl:for-each can be a child element of the xsl:stylesheet element.

2. True or False: xsl:choose must contain an xsl:otherwise element.

3. What is the difference between selecting data and matching data?

4. What is the difference between using multiple xsl:if elements after another and xsl:when elements with the same test expressions inside an xsl:choose element?

5. Is there a difference between expressions within a predicate and expressions used with the test attribute of the xsl:if and xsl:when elements?

Exercises

1. From the following XML, create a stylesheet that rearranges the document so that the cars are grouped by year. To do so, use xsl:for-each and predicate expressions.

<?xml version=″“1.0”" encoding="UTF-8"?>
<cars>
  <car model="Focus" manufacturer="Ford" year="2000" />
  <car model="Golf" manufacturer="Volkswagen" year="1999" />
  <car model="Camry" manufacturer="Toyota" year="1999" />
  <car model="Civic" manufacturer="Honda" year="2000" />
  <car model="Prizm" manufacturer="Chevrolet" year="2000" />
</cars>

2. Create a stylesheet for the XML from Exercise 1 to add an attribute named country to each element. For Ford and Chevrolet, this attribute should read 'USA'; for Honda and Toyota, 'Japan'; and for Volkswagen, 'Germany'. Use either xsl:if or xsl:choose.

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

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