Working with Expressions

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

Boolean expressions are those whose result is determined by applying one of the following operations

  • Equality— Something = something else.

  • Less than, or less than or equal to— Something < something else (really &lt; and &lt;=).

  • 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).

Code Listing 5.5. Land.xsl—Select Land Only Listings
 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:

Code Listing 5.6. first.xsl—Select the First Listing
 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!

Code Listing 5.7. last.xsl—Select the Last Listing
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.

Table 5.3. Node Set Functions
Function Description
count() Returns a number equal to the total number of elements in the node set.
id(someid) Returns a node set of nodes that match the given id. We could then act directly on the result node set and perform other operations. For example, id(someid)[5] returns the 5th element whose id is someid.
last() Returns a number equal to the last element in the node set.
local-name(node set) Like name, local-name returns the name of the first element in the node set. Unlike name, local-name returns on the local part (deepest in the element hierarchy).
name(node set) Returns a string representing the complete name of the first element in the node set in the order originally given in the input document.
position() Returns a number equal to the position of the node being acted on in the node set.

Code Listing 5.8. middle.xsl—Select the Middle Listings
 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() &lt; last()]">

Table 5.4 lists the functions that return Boolean values or can be used in Boolean expressions.

Table 5.4. Boolean Functions
Function Description
boolean() Converts its argument to a Boolean value
false() Always returns false
not(expression) Inverts the value of the result
true() Always returns true

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.

Combining Expressions with Logical Operators

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.

Number Expressions

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.

Code Listing 5.9. cheap.xsl—Select Listings Less Than $150,000
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 &lt; 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.

Code Listing 5.10. cheap-2.xsl—Select Listings Less Than $150,000 with Factoring for 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 &lt; 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.

Table 5.5. Numeric Functions
Function Description
ceiling() Returns the smallest (closest to negative infinity) number that is not less than the argument and that is an integer.
floor() Returns the largest (closest to positive infinity) number that is not greater than the argument and that is an integer.
number() Convert the given object to a number. true converts to 1, false to 0. Strings are converted to numbers by stripping whitespace and then proceeding as normal. Other objects are converted in a fashion appropriate for the object.
round() Returns the number that is closest to the argument and that is an integer. If there are two such numbers, the one that is closest to positive infinity is returned.
sum() Sums the arguments by first calling string to convert to a string and then converting to a number and adding the result.
true() Always returns true.

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.

Code Listing 5.11. avgprice.xsl—Average Real Estate Listing Prices
 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>&#xD;&#xA;</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>

Code Listing 5.12. Applying avgprice.xsl to relisting.xml
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.

Code Listing 5.13. numbertext.xsl—Sample Conversions
<?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>

Code Listing 5.14. Result of Running numbertest.xsl Using XT
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

String Expressions

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>

Table 5.6. String Functions
Function Description
concat(str1,str2[...]) Returns a string representing appending str2 to str1. concat() can work on more than two strings.
contains(sourcestr, matchstr) Returns true if the source string contains the matchstr.
substring(sourcestr, start[,len]) Returns the portion of sourcestr starting at start for len. The length is optional. Start is 1-based.
substring-after(sourcestr, matchstr) Returns the portion of sourcestr that occurs after matchstr. For example, substring-after('200 Forest Street,Marlboro, Mass',',') returns Marlboro, Mass.
substring-before(sourcestr, matchstr) Returns the portion of sourcestr that occurs before matchstr. For example, substring-before('200 Forest Street,Marlboro, Mass',',') returns 200 Forest Street.
starts-with(sourcestr, matchstr) Returns true if the source string starts with the match string.
string() Converts the given argument to a string.
string-length(string) Returns the length of the string.
normalize(string) Returns a string with leading and trailing whitespace stripped and internal whitespace compressed to a single space.
translate(sourcestr, targetstr, replacestr) Returns sourcestr with all instances of targetstr replaced with replacestr.

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.

Node Set Expressions

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

Table 5.7. Abbreviations for the XPath Axes
Axis Description
child Contains all the children of the current node.
descendant Contains all the descendants of the current node—children, grandchildren, great-grandchildren, and so on.
parent The parent of the current node.
ancestor The parent, grandparent, great-grandparent, and so on of the current node.
following-sibling Contains the next sibling of the current node. Attributes and namespace elements do not have siblings, and this axis returns an empty node set if applied to either.
preceding-sibling Contains the previous sibling of the current node. Attributes and namespace elements do not have siblings, and this axis returns an empty node set if applied to either.
following Contains all nodes, in document order, after the current node, excluding descendants, attributes, and namespace elements.
preceding Contains all nodes, in document order, before the current node, excluding descendants, attributes, and namespace elements.
attribute Contains the attributes of the element.
namespace Contains all the namespace nodes of the current node.
self The current or selected node.
descendant-or-self Contains the current node as well as all its descendants.
ancestor-or-self Contains the current node as well as all its ancestors.

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.

Code Listing 5.15. axis-self.xsl
<?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>

Code Listing 5.16. axis-parent.xsl
<?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>

Code Listing 5.17. axis-child.xsl
<?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>

Code Listing 5.18. axis-attribute.xsl
<?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>

Code Listing 5.19. axis-aos.xsl
<?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>

Code Listing 5.20. axis-dos.xsl
<?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

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.

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

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