Most readers are familiar with expressions. 1+1=2, a2 + b2=c2, and if (a>b) then x are all examples of simple expressions. XSL supports expressions of surprising sophistication and power. In fact, XSLT derives much of its power from its expression language. Expressions allow developers to do several things, chief of which are
Selecting nodes to be further processed
Specifying conditions for processing nodes
Generating output text
There are five types of expressions:
Node Sets— Expressions that act on node sets. Node sets are just that, unordered collections of nodes generated by applying <xsl:template match="..."> and <xsl:apply-template select="...""> rules. We've been working with node sets all along but haven't known it! There are a number of built-in functions for node sets, and we'll see them shortly.
Booleans— Boolean expressions are those that result in either a true or false result.
Strings— A string is just that, a string! More precisely, a string is a set of Unicode characters. The string() built-in can be used to convert a node, node set, result tree, Boolean, or number to a string.
Numbers— Numbers are 64-bit IEEE floating-point double values. The number() built-in function can convert various types to numbers. Numbers can be combined in expressions in the normal way using +, -, *, div, and so on (can't use /).
Result tree fragments— Result tree fragments are portions of XML documents that are not complete and are often not well formed.
XSLT uses the powerful expression language defined by XPath. Expressions can be used to select specific nodes, specify conditions for processing nodes (perhaps in different ways), and generate output.
In addition, it's important to understand the context within which the expression is evaluated. Two specific points are important:
The context comes from the currently selected node.
The position of the current node comes from its position in its current context. A node might be the fifth in the document but the second in a particular context.
Let's now look at each of these expression types in detail.
Boolean expressions are those whose result is determined by applying one of the following operations
Less than, or less than or equal to— Something < something else (really < and <=).
Greater than, or greater than or equal to— Something > something else (> or >=).
We've already seen an example using equals, but another wouldn't hurt. Listing 5.5 shows how we might select listings that pertain only to land (because we'd rather build than buy).
1: <?xml version="1.0"?> 2: <xsl:stylesheet 3: xmlns:xsl="http://www.w3.org/XSL/Transform/1.0" 4: indent-result="no" default-space="strip"> 5: <xsl:template match="text()"/> 6: <xsl:template match="Type[@PropType='land']"> 7: land:<xsl:value-of select=".."/> 8: </xsl:template> 9: </xsl:stylesheet> 10: |
The two lines or importance here are 6 and 7. Line 6 simply says match any Type elements that have a PropType attribute whose value equals (=) the literal land. Line 7 is interesting only because it selects the parent of the property type, which is a Listing element.
Listings 5.6–5.8 show that we could, in a similar fashion, select the first, last, and anything but the first and last Listing elements:
1: <?xml version="1.0"?>
2: <xsl:stylesheet
3: xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
4: indent-result="no" default-space="strip">
5: <xsl:template match="text()"/>
6: <xsl:template match="Listing[1]">
7: First:<xsl:value-of select="."/>
8: </xsl:template>
9: </xsl:stylesheet>
10:
|
Line 6 illustrates the number grammar rule and selects the first Listing element. In fact, the [1] could have been the nth element!
1: <?xml version="1.0"?>
2: <xsl:stylesheet
3: xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
4: indent-result="no" default-space="strip">
5: <xsl:template match="text()"/>
6: <xsl:template match="Listing[position() = last()]">
7: Last:<xsl:value-of select="."/>
8: </xsl:template>
9:</xsl:stylesheet>
|
Line 6 in Listing 5.7 combines the or and function rules to select the last Listing element. Table 5.3 lists the primary functions that can be used on node sets.
1: <?xml version="1.0"?>
2: <xsl:stylesheet
3: xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
4: indent-result="no" default-space="strip">
5: <xsl:template match="text()"/>
6: <xsl:template match="Listing[not(position()=1 or position() = last())]">
7: Middle(not first or last):<xsl:value-of select="."/>
8: </xsl:template>
9: </xsl:stylesheet>
10:
|
Line 6 of Listing 5.8 actually shows several things. First, it shows that we can combine expressions using or. Second, line 6 shows that we can establish precedence or order of evaluation using parentheses, and third that we can use the not operator to negate the value of an expression. Finally, we could have written the "middle" expression more simply using > and <, as follows:
<xsl:template match="Listing[position() > 1 or position() < last()]">
Table 5.4 lists the functions that return Boolean values or can be used in Boolean expressions.
In general we see that
Boolean expressions can be either true or false.
Boolean expressions on node sets are true if and only if after converting both sets to strings, the result of applying the comparison is true.
Two strings are true if and only if they contain the exact same sequence of characters.
A non-zero length string is true. A zero length string is false.
An empty node set is false; a nonempty set, true.
If at least one element is a number, the other is converted to a number and the value of the expression is based on the resulting comparison.
We can convert an expression to a Boolean using the boolean() built-in function.
Before we go on, let's digress just a moment to see how expressions can be combined. We'd first like to examine our expressions. In fact, when we examined the default template rule for processing instructions and comments, we saw that the rule applied to processing-instructions() | comments()—that is, processing instructions or comments. The logical "or" operator, represented by the vertical bar (|), can be used to apply this template if either the first expression or the second expression is true. In fact, there is nothing to stop us from combining any of the rules we've seen before using or. For example, and it's quite a stretch, if we wanted to output as bold HTML any processing instructions, comments, ListingBrokers who are descended from County, or Prices, we might write
<xsl:template match="Price|County//ListingBroker | processing-instruction() | comment()> <xsl:value-of select="."/> </xsl:template>
We could also combine expressions using and. For example, if we were interested in nodes that had two specific attributes, say AttrA and AttrB, we might write the following:
<xsl:template match="SomeNode[@AttrA and @AttrB]"> <xsl:value-of select="."/> </xsl:template>
Note that I used whitespace, although inconsistently, within the match expression. Whitespace is ignored, so how it's used is up to you.
Let's move on and examine each of the expression types more closely.
Expressions involving numbers and numeric operations are perhaps even simpler than Boolean expressions. There are a number of operations that can be used with numbers. First, all the standard operations—such as + (plus), - (minus) and * (multiplication)—can be used. div is used for division because we can't use a slash (/). mod takes the remainder after division. For example, 9 mod 3 is 0, but 10 mod 3 is 1. Most often, we will use number expressions in <xsl:template elements. For example, Listing 5.9 outputs listings that are priced less that $150,000 (specifically via line 6). However, Listing 5.10 shows how we could use them in xsl:value-of elements as well.
1: <?xml version="1.0"?>
2: <xsl:stylesheet
3: xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
4: indent-result="no" default-space="strip">
5: <xsl:template match="text()"/>
6: <xsl:template match="Listing[ListingPrice < 150000]">
7: Cheap!:<xsl:value-of select="."/>
8: </xsl:template>
9: </xsl:stylesheet>
|
We all know that listing price in only one factor in buying a home. Taxes are a fact of life, and property taxes are one more factor determining the perceived value of a property. Listing 5.10 factors in tax rates.
1: <?xml version="1.0"?>
2: <xsl:stylesheet
3: xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
4: indent-result="no" default-space="strip">
5: <xsl:template match="text()"/>
6: <xsl:template match="Listing[ListingPrice < 150000]">
7: Cheap:<xsl:value-of select="."/>
8: taxrate:<xsl:value-of select="../TaxRate"/>
9: applied rate:$<xsl:value-of select="round((ListingPrice div 1000) * ..
/TaxRate)"/> a year
10: </xsl:template>
11: </xsl:stylesheet>
|
Line 9—the meat of this example—selects for output the current listing price in thousands times the tax rate to give the estimated yearly taxes. Breaking line 9 down even further, we can see that we used one of the numeric functions to round the result to the nearest dollar. Table 5.5 lists the various functions that can be applied to numeric results.
In fact, we could put many of the ideas we've come across to work and determine the average property value in all our listings. Listing 5.11 does just that. Listing 5.12 shows the result.
1: <?xml version="1.0"?> 2: <xsl:stylesheet 3: xmlns:xsl="http://www.w3.org/XSL/Transform/1.0" 4: indent-result="no" default-space="strip"> 5: <xsl:template match="text()"/> 6: <xsl:template match="/REListing"> 7: <xsl:for-each select="//Listing"> 8: <xsl:value-of select="ListingPrice"/><xsl:text>
</xsl:text> 9: </xsl:for-each> 10: Total value: <xsl:value-of select="sum(//ListingPrice)"/> 11: Count of properties: <xsl:value-of select="count(//ListingPrice)"/> 12: Average Property Cost: <xsl:value-of select="floor(sum(//ListingPrice) div count(/ /ListingPrice))"/> 13: </xsl:template> 14: </xsl:stylesheet> |
189000 . . . 529900 Total value: 5,105,399 Count of properties: 11 Average Property Cost:464127 |
The interesting portions of Listing 5.11 are lines 10–12. Lines 7, 8, and 9 show the <xsl:for-each. . . >, which printed all prices (I used this as a debugging aid as the average seemed very high to me when I first ran this example). Line 10 simply sums all the ListingPrice children of the REListing element. Line 11 does the same, counting up the Listing elements. Line 12 combines the two and uses the floor() function to truncate the result to whole dollars.
In general we see that
A number represents an IEEE 64-bit floating point number. This representation includes values for NaN (Not a number) positive, negative infinity, and positive and negative zero.
Applying +, -, *, div, mod, and the numeric built-in functions produces a result that is a number.
Boolean true is 1 and false is 0.
Strings are converted to numbers by trimming all leading and trailing whitespace and then converting to a number. Strings that make no sense after conversion convert as 0.
We can convert an expression to a number using the number() built-in function.
Listing 5.13 shows a simple stylesheet that executes a number of sample conversions using the number() built-in function.
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0" indent-result="no" default-space="strip"> <xsl:template match="text()"/> <xsl:template match="/"> Number test for ('123') :<xsl:value-of select="number('123')"/> Number test for ('1,230') :<xsl:value-of select="number('1,230')"/> Number test for ('-123.00') :<xsl:value-of select="number('-123.00')"/> Number test for ('123.45') :<xsl:value-of select="number('123.45')"/> Number test for (' +123.45 ') :<xsl:value-of select="number('+123.45')"/> Number test for (true()) :<xsl:value-of select="number(true())"/> Number test for (false()) :<xsl:value-of select="number(false())"/> </xsl:template> </xsl:stylesheet> |
Number test for ('123') :123 Number test for ('1,230') :NaN Number test for ('-123.00') :-123 Number test for ('123.45') :123.45 Number test for (' +123.45 ') :123.45 Number test for (true()) :1 Number test for (false()) :0 |
At this point, we've seen Boolean and numeric expressions. We can also write quite sophisticated expressions based on strings. At first glance, strings seem somewhat plain in comparison to number and Boolean expressions, but we can perform a number of operations on strings and string expressions in general. Table 5.6 lists the various functions that can be used on strings. For example, suppose we only wanted to output the first 20 characters of a listing broker? The following rule does the trick:
<xsl:template match="//Listing"> Listing brokers truncated to 20 characters:<xsl:value-of select="substring (ListingBroker,0,20)"/> </xsl:template>
In general we see that
A number is converted to a string as follows: NaN converts to "NaN"; -zero and +zero convert to "0"; positive infinity converts to "+Infinity", likewise "-Infinity".
Boolean values translate into the English word "true" or "false."
Node sets are converted to strings concatenating together all the string values of each underlying element in original document order.
Basically a node set is simply an unordered list of nodes. We've seen expressions involving node sets before. In fact, most of the early part of this chapter involved expressions using node sets. The following template rule was one of the first we encountered and, in fact, is an expression for selecting the root node REListing. Table 5.3 listed a number of built-in functions that operate on node sets.
<xsl:template match="/REListing"> . . . </xsl:template>
Node set expressions are often thought of in terms of an axis. An axis describes the tree relationship between the current selected or context element and its various ancestors and descendants. Table 5.7 lists all the axis names and their descriptions
Listings 5.15–5.20 show various axes in action, as well as their abbreviations where applicable. Note that these listings have been abbreviated somewhat. The CD-ROM versions contain the complete listings.
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0" indent-result="no" default-space="strip"> . . . <xsl:template match="/REListing//ListingBroker"> .:<xsl:value-of select="."/> self::node()/TownName:<xsl:value-of select="self::node()"/> <xsl:apply-templates/> </xsl:template> </xsl:stylesheet> |
<?xml version="1.0"?> . . . <xsl:template match="/REListing//Listing"> ../TownName:<xsl:value-of select="../TownName"/> parent::node()/TownName:<xsl:value-of select="parent::node()/TownName"/> </xsl:template> </xsl:stylesheet> |
<?xml version="1.0"?> . . . <xsl:template match="/REListing"> child::Header:<xsl:value-of select="child::Header"/> abbrev:<xsl:value-of select="Header"/> </xsl:template> </xsl:stylesheet> |
<?xml version="1.0"?> . . . <xsl:template match="/REListing/County"> attribute::Name:<xsl:value-of select="attribute::Name"/> abbrev(@Name):<xsl:value-of select="@Name"/> </xsl:template> </xsl:stylesheet> |
<?xml version="1.0"?> . . . <xsl:template match="/REListing/County/Town/Listing"> <xsl:value-of select="ancestor-or-self::node()/child::State"/> </xsl:template> </xsl:stylesheet> |
<?xml version="1.0"?> . . . <xsl:template match="/"> /descendant-or-self::node()/child::ListingBroker<xsl:value-of select=" /descendant-or-self::node()/child::ListingBroker"/> //ListingBroker:<xsl:value-of select="//ListingBroker"/> </xsl:template> </xsl:stylesheet> |
In general, we see that
Node SetsExpression may be empty
Node sets can be converted to Booleans, strings, or numbers, as previously stated.
Node sets can be acted on to generate other node sets based on their relationship to other nodes and elements.
Node sets can be specified using axes or abbreviations for axes.
There is no abbreviation for some axes, such as ancestor or ancestor-or-self.
The most common abbreviations for axes are
. for self::node()
ElementName for child::ElementName
../SomeElement for parent::SomeElement
@AttributeName for attribute::AttributeName
// for /descendant=or-self::node()/
Axes and axes abbreviations can be combined in the same expression.
Result tree fragment expressions are perhaps the least useful of all expressions, and we only mention them for the sake of completeness. A result tree fragment is the tree that results from creating an XML document on-the-fly. Such a tree is not well formed. Because the tree is not well formed, we can't apply most of the previous expressions to it. In fact, the only expressions we can apply are boolean() and string(), which return true or false, respectively, depending on whether the fragment is populated or not.
3.22.119.251