Creating Predicates

Creating Predicates

Predicates are true XPath expressions, and XPath is much more of a true language than patterns; for example, XPath expressions can return not only lists of nodes, but also Boolean, string, and numeric values. XPath expressions are not restricted to working with the current node or child nodes, because you can work with parent nodes, ancestor nodes, and more.

Chapter 7 gives its full attention to XPath, but its worth getting an introduction to the subject during this discussion about patterns, because the predicate part of a pattern is its most powerful part. There are all kinds of expressions that you can work with in predicates; the following list includes some possible types, which are explored in the next sections:

  • Node-sets

  • Booleans

  • Numbers

  • Strings

Predicates: Node Sets

As its name implies, a node set is simply a set of nodes (and it may contain only a single node). The expression child::PLANET returns a node set of all <PLANET> elements. The expression child::PLANET/child::NAME returns a node list of all <NAME> elements that are children of <PLANET> elements. To select a node or nodes from a node set, you can use the following functions that work on node sets in predicates.

  • last() . Returns the number of nodes in a node set.

  • position() . Returns the positi on of the context node in the context node set (starting with 1).

  • count(node-set) . Returns the number of nodes in a node set. Omitting node-set makes this function use the context node.

  • id(string ID) . Returns a node set containing the element whose ID matches the string passed to the function, or an empty node set if no element has the specified ID. You can list multiple IDs separated by white space, and this function returns a node set of the elements with those IDs.

  • local-name(node-set) . Returns the local name of the first node in the node set. Omitting node-set makes this function use the context node.

  • namespace-uri(node-set) . Returns the URI of the namespace of the first node in the node set. Omitting node-set makes this function use the context node.

  • name(node-set) . Returns the full, qualified name of the first node in the node set. Omitting node-set makes this function use the context node.

Heres an example; in this case, I number the elements in the output document using the position() function:

Listing 4.6 Using the position Function
 <?xml version="1.0"?>  <xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">      <xsl:template match="PLANETS">          <HTML>              <HEAD>                  <TITLE>                      The Planets                  </TITLE>              </HEAD>              <BODY>                  <xsl:apply-templates select="PLANET"/>              </BODY>          </HTML>      </xsl:template>      <xsl:template match="PLANET">          <P>              <xsl:value-of select="position()"/>.              <xsl:text> </xsl:text>              <xsl:value-of select="NAME"/>          </P>      </xsl:template>  </xsl:stylesheet> 

Heres the result, where you can see that the planets are numbered:

 <HTML>      <HEAD>          <TITLE>              The Planets          </TITLE>      </HEAD>      <BODY>          <P>              1. Mercury          </P>          <P>              2. Venus          </P>          <P>              3. Earth          </P>      </BODY>  </HTML> 

You can also use functions that operate on node sets in predicates, as I do here: PLANET[position() = last()] , which selects the last <PLANET> child of the context node.

Predicates: Booleans

You can also use Boolean values in XPath expressions. Numbers are considered false if theyre zero, and true otherwise . An empty string, , is also considered false, and all other strings are considered true.

You can use the following XPath logical operators to produce Boolean true/false results:

  • ! = means is not equal to

  • < means is less than (use &lt; in XML or XSL documents)

  • <= means is less than or equal to (use &lt;= in XML or XSL documents)

  • = means is equal to (C, C++, Java, JavaScript programmers take note: this operator is one = sign, not two).

  • > means is greater than

  • >= means is greater than or equal to

Using the < Character

Note in particular that you shouldnt use < directly in XML or XSL documents, so you should use the entity reference &lt; instead.

You can also use the keywords and and or to connect Boolean clauses with a logical And or Or operation, and you can use not to flip the logical sense of an expression, from true to false, or false to true.

In the following example, I identify Earths <PLANET> element and place the strings "Earth" , "needs" , "no" , and "introduction" in the table rather than Earths numeric data. I identify which planet is Earth with the predicate "[NAME='Earth']" , which checks the value of the <NAME> element, which in turn is its enclosed text. I also provide a template for the other planets, matching the predicate "[NAME!='Earth']" :

Listing 4.7 Finding Planet Earth
 <?xml version="1.0"?>  <xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">      <xsl:template match="/PLANETS">          <HTML>              <HEAD>                  <TITLE>                      The Planets Table                  </TITLE>              </HEAD>              <BODY>                  <H1>                      The Planets Table                  </H1>                  <TABLE BORDER="2">                      <TR>                          <TD>Name</TD>                          <TD>Mass</TD>                          <TD>Radius</TD>                          <TD>Day</TD>                      </TR>                      <xsl:apply-templates/>                  </TABLE>              </BODY>          </HTML>      </xsl:template>      <xsl:template match="PLANET[NAME='Earth']">         <TR>            <TD>Earth</TD>            <TD>needs</TD>            <TD>no</TD>            <TD>introduction.</TD>         </TR>     </xsl:template>      <xsl:template match="PLANET[NAME!='Earth']">         <TR>            <TD><xsl:value-of select="NAME"/></TD>            <TD><xsl:apply-templates select="MASS"/></TD>            <TD><xsl:apply-templates select="RADIUS"/></TD>            <TD><xsl:apply-templates select="DAY"/></TD>         </TR>     </xsl:template>      <xsl:template match="MASS">          <xsl:value-of select="."/>          <xsl:text> </xsl:text>          <xsl:value-of select="@UNITS"/>      </xsl:template>      <xsl:template match="RADIUS">          <xsl:value-of select="."/>          <xsl:text> </xsl:text>          <xsl:value-of select="@UNITS"/>      </xsl:template>      <xsl:template match="DAY">          <xsl:value-of select="."/>          <xsl:text> </xsl:text>          <xsl:value-of select="@UNITS"/>      </xsl:template>  </xsl:stylesheet> 

Heres the result:

 <HTML>      <HEAD>          <TITLE>              The Planets Table          </TITLE>      </HEAD>      <BODY>          <H1>              The Planets Table          </H1>          <TABLE BORDER="2">              <TR>                  <TD>Name</TD>                  <TD>Mass</TD>                  <TD>Radius</TD>                  <TD>Day</TD>              </TR>              .              .              .              <TR>                  <TD>Earth</TD>                  <TD>needs</TD>                  <TD>no</TD>                  <TD>introduction.</TD>              </TR>          </TABLE>      </BODY>  </HTML> 

You can see this result in Figure 4.1.

Figure 4.1. Using XPath predicates.
graphics/04fig01.gif

The following example uses the logical operator >. This rule applies to all <PLANET> elements after position 5:

 <xsl:template match="PLANET[position() > 5]">      <xsl:value-of select="."/>  </xsl:template> 

There is also a true function that always returns a value of true, and a false function that always returns a value of false. You can also use the not function to reverse the logical sense of an expression, as in the following case, where Im selecting all but the last <PLANET> element:

 <xsl:template match="PLANET[not(position() = last())]">      <xsl:value-of select="."/>  </xsl:template> 

Finally, the lang function returns true or false depending on whether the language of the context node (which is given by xml:lang attributes) is the same as the language you pass to this function.

Predicates: Numbers

In XPath, numbers actually are stored in double floating point format. (Technically speaking, all XPath numbers are stored in 64-bit IEEE 754 floating-point double format.) All numbers are stored as doubles, even integers such as 5, as in the example you just saw:

 <xsl:template match="PLANET[position() > 5]">      <xsl:value-of select="."/>  </xsl:template> 

You can use several operators on numbers:

  • + addition

  • - subtraction

  • * multiplication

  • div division (the / character, which stands for division in other languages, is already heavily used in XML, XSL, and XPath)

  • mod returns the modulus of two numbers (the remainder after dividing the first by the second).

For example, the element <xsl:value-of select="180 + 420"/> inserts the string 600 into the output document. The following example selects all planets whose day (measured in Earth days) divided by its mass (where the mass of the earth = 1) is greater than 100:

 <xsl:template match="PLANETS">      <HTML>          <BODY>              <xsl:apply-templates select="PLANET[DAY div MASS > 100]"/>          </BODY>      </HTML>  </xsl:template> 

XPath also supports these functions that operate on numbers:

  • ceiling() . Returns the smallest integer larger than the number you pass it.

  • floor() . Returns the largest integer smaller than the number you pass it.

  • round() . Rounds the number you pass it to the nearest integer.

  • sum() . Returns the sum of the numbers you pass it.

For example, heres how you can find the average mass of the planets in planets.xml:

Listing 4.8 Finding Average Planetary Mass
 <?xml version="1.0"?>  <xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <xsl:output method="xml"/>  <xsl:template match="PLANETS">      <HTML>          <BODY>              The average planetary mass is:  <xsl:value-of select="sum(child::PLANET/child::MASS) div count(child::PLANET)"/>          </BODY>      </HTML>  </xsl:template>  </xsl:stylesheet> 

Strings

In XPath, strings are made up of Unicode characters , as youd expect. A number of functions are specially designed to work on strings:

  • string(object object1) . Converts an object into a string.

  • starts-with (string string1, string string2) . Returns true if the first string starts with the second string.

  • contains(string string1, string string2) . Returns true if the first string contains the second one.

  • substring(string string1, number offset, number length) . Returns length characters from the string, starting at offset .

  • substring-before (string string1, string string2) . Returns the part of string1 up to the first occurrence of string2 .

  • substring-after(string string1, string string2) . Returns the part of string1 after the first occurrence of string2 .

  • string-length (string string1) . Returns the number of characters in string1 .

  • normalize-space(string string1) . Returns string1 after leading and trailing whitespace is stripped and multiple consecutive whitespace is replaced with a single space.

  • translate(string string1, string string2, string string3) . Returns string1 with all occurrences of the characters in string2 replaced by the matching characters in string3 .

  • concat(string string1, string string2,...) . Returns all strings concatenated (that is, joined) together.

And theres another string function you should know about that is actually part of XSLT, not XPath:

  1. format-number(number number1, string string2, string string3) . Returns a string holding the formatted string version of number1 , using string2 as a formatting string (you create formatting strings as you would for Javas java.text.DecimalFormat method) and string3 as the optional locale string.

In the following example, I match text nodes whose text starts with E to match the Earth, and add the text (the World) to the description of Earth, making it Earth (the World). To do that, I use the predicate "text()[starts-with(., 'E')]" :

Listing 4.9 Using the starts-with Function
 <?xml version="1.0"?>  <xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">      <xsl:template match="/PLANETS">          <HTML>              <HEAD>          .          .          .              </BODY>          </HTML>      </xsl:template>      <xsl:template match="PLANET">         <TR>            <TD><xsl:apply-templates select="NAME"/></TD>            <TD><xsl:apply-templates select="MASS"/></TD>            <TD><xsl:apply-templates select="RADIUS"/></TD>            <TD><xsl:apply-templates select="DAY"/></TD>         </TR>     </xsl:template>      <xsl:template match="text()[starts-with(., 'E')]">          <xsl:text>(the World)</xsl:text>      </xsl:template>      <xsl:template match="NAME">          <xsl:value-of select="."/>          <xsl:text> </xsl:text>          <xsl:value-of select="@UNITS"/>          <xsl:apply-templates/>      </xsl:template>          .          .          .      <xsl:template match="DAY">          <xsl:value-of select="."/>          <xsl:text> </xsl:text>          <xsl:value-of select="@UNITS"/>      </xsl:template>  </xsl:stylesheet> 

And heres the resultnote that the caption for Earth has become Earth (the World):

 <HTML>      <HEAD>          <TITLE>              The Planets Table          </TITLE>      </HEAD>      <BODY>          <H1>              The Planets Table          </H1>          <TABLE BORDER="2">              <TR>                  <TD>Name</TD>                  <TD>Mass</TD>                  <TD>Radius</TD>                  <TD>Day</TD>              </TR>              .              .              .              <TR>                  <TD>Earth (the World)</TD>                  <TD>1 (Earth = 1)</TD>                  <TD>2107 miles</TD>                  <TD>1 days</TD>              </TR>          </TABLE>      </BODY>  </HTML> 

You can see this document in Figure 4.2.

Figure 4.2. Using text predicates.
graphics/04fig02.gif

Predicates: Result Tree Fragments

XSLT 1.0 adds result tree fragments to the data types supported by XPath. Result tree fragments are tree fragments that you could assign to XSLT variables , and arent put to much use. About all you can do with them is evaluate their string value. Support for them was removed in the XSLT 1.1 working draft, so presumably, theyll no longer be in use in XSLT 2.0.

Abbreviated Syntax for Predicates

You can abbreviate predicate expressions by omitting "position() =" if you like. For example, [position() = 3] becomes [3] , [position() = last()] becomes [last()] , and so on. Using the abbreviated syntax makes XPath expressions in predicates much easier to use. Here are some examples:

  • PLANET[2] . Returns the second <PLANET> child of the context node.

  • PLANET[last()] . Returns the last <PLANET> child of the context node.

  • /PLANETS/PLANET[2]/NAME[1] . Returns the first <NAME> element of the second <PLANET> element of the <PLANETS> element.

  • PLANET[5][@UNITS = "million miles"] . Returns the fifth <PLANET> child of the context node, only if that child has a UNITS attribute with value "million miles" . Can also be written as PLANET[@UNITS = "million miles"][5] .

That completes this look at the three parts of step patterns: axes, node tests, and predicates. They are the building blocks of match patterns. The best way to learn how to create patterns is by example, and many examples are coming up. First, though, its important to cover one or two quick topics. As you may recall from the discussion of the formal definition of a match pattern, you can create patterns that match elements by ID or by key in addition to using step patterns.



Inside XSLT
Inside Xslt
ISBN: B0031W8M4K
EAN: N/A
Year: 2005
Pages: 196

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net