WEEK 1 Day 19
Working with XSLT Extensions

In yesterday’s lesson, you learned that although you can perform computations with XSLT, creating computations is difficult because of the lack of mathematical functions that you usually find in programming languages. Fortunately, XSLT is extensible, and many processors offer additional functionality that is not part of the XSLT specification itself but can make your life a whole lot easier. Some processors also enable you to write your own extensions.

Today’s lesson looks at what extensions are and how you can use them. You also will learn about some common extensions in different processors. Last but not least, you will learn how to create your own extensions.

In today’s lesson, you will learn the following:

• What an extension is

• How extensions make stylesheets simpler

• What the drawbacks of extensions are

• How to use common extensions

• How to create your own extensions

Understanding XSLT Extensions

XSLT extensions come into play when XSLT’s functionality lacks the capability to do something or when performing a task is so complex in XSLT that the easiest way is to use functionality from outside XSLT. But what exactly are extensions?

What Are XSLT Extensions?

The XSLT specification defines the elements and functions available in XSLT. Basically, if you’re talking about XSLT, you’re talking about these elements and functions. Although these elements and functions offer you a myriad of possibilities, they are lacking in certain areas, most notably mathematical processing. Fortunately, XSLT is designed to be extensible, so users or vendors can create extensions that provide you with additional functionality that can make complex tasks easy and impossible tasks possible. Extensions exist in three forms:

• Extension elements, which are elements that normally are not part of XSLT

• Extension attributes, which are attributes that normally are not part of the elements defined in XSLT

• Extension functions, which are functions that normally are not part of XSLT

What Are the Benefits of XSLT Extensions?

XSLT extensions provide you with a mechanism to extend the model that XSLT provides. The key benefit of extensions, therefore, is that they enable you to solve problems that are otherwise impossible to solve or take an immense amount of code. Especially in mathematical processing, you can extend XSLT so that you can use the basic mathematical functions available in other languages. In these languages, such functionality often requires no more than just one or two lines of code, whereas the same operation in XSLT can require tens or even hundreds of lines of code, if the functionality can be achieved with pure XSLT in the first place. A simple function such as calculating the square root of a number is nearly impossible to create with pure XSLT. The algorithm for this function is quite complex. Most languages, such as C++, Java, and Visual Basic, however, have this function built in, and invoking it from XSLT takes just a few lines of code. In fact, invoking this function is so simple that you might question the decision to leave it out of XSLT in the first place. However, that’s the way things are, so you need to deal with them.

Another major benefit of XSLT extensions, probably the reason they were created to begin with, is that they enable you to create applications that are very specific to a certain area—for instance, applications dealing with chemistry. Just like you can define vocabularies that are specifically suited for these application domains, you can create extensions that perform functions specific to that domain, possibly on the basis of the vocabulary that you created. On Day 7, “Controlling the Output,” you learned about applications that output Portable Document Format (PDF) or Rich Text Format (RTF). These applications can be implemented as extensions to XSLT, so you can create PDF or RTF documents straight from the XSLT processor, instead of having to use an additional application or component in your application.

The Drawbacks of XSLT Extensions

XSLT extensions are handy, but they have one major drawback: Extensions are processor specific. This means that if you create an extension for Saxon, the same extension can’t be used with Xalan or Microsoft’s MSXML component. This incompatibility tosses the concept of cross-platform, cross-processor XSLT right out the window. Any stylesheet that uses specific extensions implemented on a specific parser can’t be run with any other processor. XSLT 1.1 addresses this drawback for extension functions up to a certain degree: With XSLT 1.1, the xsl:script element enables you to create extension functions within a stylesheet using ECMAScript, JavaScript, or Java. With the same element, you also can reference a separate source document that defines an extension function.

Note

XSLT 1.1 development has been abandoned, so it will probably never reach W3C Recommendation status. Some processors, such as Saxon, have implemented several XSLT 1.1 features, some of which differ from XSLT 1.0.

Some processors currently offer an extension element that serves the same function as the XSLT 1.1 xsl:script element. You can use the vendor-specific script elements to create extension functions. Because these elements are processor specific, you do need to create a function multiple times, one for each processor. You also have to check which one you should use at runtime. In the following sections, you will learn how to do both of these things.

Tip

Sticking to XSLT as much as possible is always a good idea. If you can solve a problem without resorting to extensions, go for it! Use extensions only if you can save a lot of time or if you can’t achieve something by using pure XSLT.

Another point to keep in mind is that programming with extensions isn’t always as easy as it looks. Certain coding mistakes can cause your whole stylesheet to produce no output at all. You just draw a blank, no errors, nothing. This type of response isn’t true for every type of mistake and probably not with all processors, but it is something to look out for because tracking the problem in these cases is very hard. If you still decide to use extensions, start with something simple to check whether the result is what you want. Slowly build your stylesheet to make sure that everything works. If you get into a situation in which the stylesheet ceases to function, you know that the last change you made caused the error, so you know where to look.

How Do XSLT Extensions Work?

XSLT extensions operate separately from the core XSLT elements and functions. To achieve this separation, extension elements, attributes, and functions need to use a separate namespace. When the processor encounters an element, attribute, or function that is part of that namespace, the function is invoked more or less as if it is part of XSLT. When this situation occurs with a function defined within the stylesheet itself with a script element, the processor simply needs to invoke that script. Extension elements, attributes, or functions that depend on an external code library are somewhat trickier because the processor needs to know how to invoke that code library. With products such as Saxon, the value of the namespace name determines how to invoke the code library. Specifically, elements need to implement a specific interface so that the processor knows how to call it.

Note

Building your own extension elements is a complex task requiring knowledge and experience with programming and the underlying platform. It is beyond the scope of this book to discuss creating extension elements. This book will discuss only creating your own extension functions with the script element.

Using Built-in Extensions

To distinguish extensions from regular XSLT constructs, the elements, attributes, or functions need to use a namespace that is separate from the namespace used by XSLT. You need to declare this namespace in the xsl:stylesheet element. In addition, you need to add the namespace prefix of the declared namespace to the value of the extension-element-prefixes attribute. This attribute tells the processor that the given namespace prefix or prefixes denote extension elements, attributes, or functions. The value of the attribute should be a whitespace-separated list of namespace prefixes. The following xsl:stylesheet element adds Saxon extensions to the stylesheet:

<xsl:stylesheet version=″“1.0”"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:saxon="http://icl.com/saxon"
    extension-element-prefixes="saxon">

The prefix saxon is used for the Saxon extension. Because this is a regular namespace, you can use any other nondefault prefix, as long as the namespace name is the same. If you want to use extensions with a different processor—for instance, MSXML—the value of the namespace name is different. For MSXML, the stylesheet start tag should look like this:

<xsl:stylesheet version=″“1.0”"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt">

Microsoft has chosen to use a Uniform Resource Name (URN) instead of a Uniform Resource Locator (URL) to declare the namespace. The principle is the same, however. Also, note that the extension-element-prefixes attribute is not defined. If you want to do everything by the book, you should add it, but MSXML detects that you’re working with extensions itself. Because most processors require the extension-element-prefixes attribute, adding it anyway is wise. Otherwise, if you change your stylesheet to another processor later, you’ll have a hard time figuring out why it isn’t working. In that case, the most likely result is that the extension elements are inserted into the result as is rather than processed.

Finally, if you want to use extensions provided by Xalan, the start tag of the stylesheet should look like this:

<xsl:stylesheet version=″“1.0”"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xalan="http://xml.apache.org/xalan"
    exclude-result-prefixes="xalan">

Xalan also doesn’t require the extension-element-prefixes attribute, but only for the built-in extensions. Note that the xalan namespace prefix is also excluded from the output explicitly with the exclude-result-prefixes attribute. If you don’t declare such an attribute, Xalan is likely to declare the namespace in any XML output. To avoid such side effects, you would be wise to add the exclude-result-prefixes attribute with all the namespaces declared for processor extensions so that they’ll never appear in any output.

Using Extension Elements

Extension elements are just like regular XSLT elements, with the exception, of course, that they are not part of the XSLT language and can be used only with specific processors. Other than that, their usage is more or less the same. Saxon provides quite a few extension elements. All of them are described in the product documentation that comes with Full Saxon.

Note

Discussing all the extension elements in Saxon is beyond the scope of this book.

The extension elements in Saxon are discussed in the file named extensions.html, which you can find in the doc directory of the Saxon installation directory. You can also find this documentation at http://users.iclway.co.uk/mhkay/saxon/saxon6.2.2/extensions.html. Documentation on the extensions in MSXML is part of the MSXML SDK, which you can download from http://msdn.microsoft.com/xml/. Xalan’s extensions are described in the file named extensions.html in the doc directory of the installation, and can also be found at http://xml.apache.org/xalan-j/extensions.html.

One extension element in Saxon that could have benefited examples in preceding lessons is the saxon:assign element. This element enables you to change the contents of a variable, getting around the problem that variables in XSLT can’t change while they are in scope. To see how this element can benefit you, let’s look at an example that creates a check just like examples from the preceding lessons. Listings 19.1 and 19.2 show the source XML documents.

LISTING 19.1 XML Source Representing an “Inventory”

<?xml version=″“1.0”" encoding="UTF-8"?>
<menu>
    <appetizers title="Work up an Appetite">
       <dish id="1" price="8.95">Crab Cakes</dish>
       <dish id="2" price="9.95">Jumbo Prawns</dish>
       <dish id="3" price="10.95">Smoked Salmon and Avocado Quesadilla</dish>
       <dish id="4" price="6.95">Caesar Salad</dish>
    </appetizers>
    <entrees title="Chow Time!">
       <dish id="5" price="19.95">Grilled Salmon</dish>
       <dish id="6" price="17.95">Seafood Pasta</dish>
       <dish id="7" price="16.95">Linguini al Pesto</dish>
       <dish id="8" price="18.95">Rack of Lamb</dish>
       <dish id="9" price="16.95">Ribs and Wings</dish>
    </entrees>
    <desserts title="To Top It Off">
       <dish id="10" price="6.95">Dame Blanche</dish>
       <dish id="11" price="5.95">Chocolat Mousse</dish>
       <dish id="12" price="6.95">Banana Split</dish>
    </desserts>
</menu>

LISTING 19.2 XML Source with Order Data Related to Listing 19.1

<?xml version=″“1.0”" encoding="UTF-8"?>
<orders>
    <order id="2" quantity="1" />
    <order id="3" quantity="2" />
    <order id="4" quantity="2" />
    <order id="5" quantity="3" />
    <order id="7" quantity="2" />
    <order id="10" quantity="1" />
    <order id="12" quantity="4" />
</orders>

Note

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

Listing 19.3 shows a stylesheet that uses the saxon:assign extension function. Because it uses this function, there is no need for recursion or a separate iteration to total the result.

LISTING 19.3 Stylesheet Using Saxon Extension Elements

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:     xmlns:saxon="http://icl.com/saxon"
5:     extension-element-prefixes="saxon"
6:     exclude-result-prefixes="saxon">
7:
8:    <xsl:output method="text" encoding="UTF-8" />
9:     <xsl:strip-space elements="*" />
10:   <xsl:variable name="dishes" select="document ('19list01.xml')//dish" />
11:    <xsl:variable name="total" select="0" saxon:assignable="yes" />
12:
13:   <xsl:template match="/">
14:     <xsl:apply-templates select="//order" />
15:     <xsl:text>&#xA;Total: $</xsl:text>
16:      <xsl:value-of select="$total" />
17:   </xsl:template>
18:
19:    <xsl:template match="order">
20:      <xsl:variable name="price"
21:                    select="$dishes[@id = current ()/@id]/@price" />
22:     <xsl:value-of select="@quantity" />
23:     <xsl:text> x </xsl:text>
24:     <xsl:value-of select="$dishes[@id = current ()/@id]" />
25:     <xsl:text>  ($</xsl:text>
26:     <xsl:value-of select="$price" />
27:      <xsl:text>) = $</xsl:text>
28:     <xsl:value-of select="format-number ($price * @quantity, '0.00')" />
29:      <xsl:text>&#xA;</xsl:text>
30:     <saxon:assign name="total" select="$total + $price * @quantity" />
31:   </xsl:template>
32: </xsl:stylesheet>

ANALYSIS

Listing 19.3 uses the saxon:assign element to alter the value of a global variable that holds the total of the entire order up to the point that the orders are processed. For each order, the subtotal is added to the total. Because the stylesheet uses Saxon extensions, line 4 declares the namespace for those extensions. Line 5 uses the extension-element-prefixes attribute to tell the processor that the declared namespace is used for extensions. The exclude-result-prefixes attribute makes sure that if the output is XML (which, in this case, it isn’t), the namespace used for the extensions is not copied to the result document. The global variable total is defined on line 11. To tell Saxon that the variable can be changed during processing, you add the saxon:assignable attribute with the value yes. If you don’t add this attribute, Saxon can’t change the value of the variable. The template on line 13, which matches the root element, does nothing else but invoke matching for the order elements on line 14, output some text, and output the final value of the total variable on line 15. The template on line 19, which matches the order elements, displays each order with the name, the number of times it was ordered, its price, and the subtotal for the dish. To make it all a bit easier to work with, line 20 creates a price variable with the price of the dish being processed. Line 30 uses the saxon:assign element to give the total variable a new value. This value is its previous value plus the subtotal for the dish being processed, calculated from the price and quantity. So, for each dish, the subtotal is added to the total. The result of this stylesheet is the same as that in Listing 18.5 and Listing 17.8, which are based on other types of calculations. The result is shown in Listing 19.4.

OUTPUT

LISTING 19.4 Result from Applying Listing 19.3 to Listing 19.2 in Saxon

1 x Jumbo Prawns  ($9.95) = $9.95
2 x Smoked Salmon and Avocado Quesadilla  ($10.95) = $21.90
2 x Caesar Salad  ($6.95) = $13.90
3 x Grilled Salmon  ($19.95) = $59.85
2 x Linguini al Pesto  ($16.95) = $33.90
1 x Dame Blanche  ($6.95) = $6.95
4 x Banana Split  ($6.95) = $27.80

Total: $174.25

ANALYSIS

As you can see from the output, using an extension element has no influence on the result. Programming Listing 19.3 is much easier than any of the other solutions, but with the disadvantage that it works only with Saxon.

Using Extension Functions

Extension functions don’t differ much from functions supported by XSLT by default. The difference is that these functions are defined for specific processors and might not conform completely to the goals of XSLT. XSLT is designed to be free of side effects, so it could be processed in parallel or incrementally (as discussed on Day 17, “Using Recursion”). With extension functions, you have no guarantee that they have no side effects. Saxon supports more than 25 extension functions, some of which are quite useful. One of the extension functions is saxon:sum (), which is similar to the sum () function provided by XSLT. The major difference is that the saxon:sum () function is not restricted to aggregating the value in the given node-set; it can also be provided with an expression to be used on the nodes in the node-set before the aggregation is done. The result of the expression executed for each node in the node-set is aggregated. Listing 19.5 shows how you can use this function to create yet another stylesheet to calculate the check from Listings 19.1 and 19.2.

LISTING 19.5 Stylesheet Using Saxon Extension Functions

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:     xmlns:saxon="http://icl.com/saxon"
5:     extension-element-prefixes="saxon"
6:     exclude-result-prefixes="saxon">
7:
8:    <xsl:output method="text" encoding="UTF-8" />
9:     <xsl:strip-space elements="*" />
10:   <xsl:variable name="dishes" select="document ('19list01.xml')//dish" />
11:
12:   <xsl:template match="/">
13:     <xsl:call-template name="displayorder">
14:       <xsl:with-param name="order">
15:         <xsl:apply-templates select="//order" />
16:       </xsl:with-param>
17:     </xsl:call-template>
18:   </xsl:template>
19:
20:   <xsl:template match="order">
21:     <item price="{$dishes[@id = current ()/@id]/@price}">
22       <xsl:copy-of select="@*" />
23:       <xsl:value-of select="$dishes[@id = current ()/@id]" />
24:     </item>
25:   </xsl:template>
26:
27:   <xsl:template name="displayorder">
28:      <xsl:param name="order" />
29:     <xsl:for-each select="$order/item">
30:       <xsl:value-of select="@quantity" />
31:        <xsl:text> x </xsl:text>
32:       <xsl:value-of select="." />
33:        <xsl:text>  ($</xsl:text>
34:        <xsl:value-of select="@price" />
35:        <xsl:text>) = $</xsl:text>
36:       <xsl:value-of select="format-number (@price * @quantity, '0.00')" />
37:        <xsl:text>&#xA;</xsl:text>
38:     </xsl:for-each>
39:      <xsl:text>&#xA;Total: $</xsl:text>
40:     <xsl:value-of select="saxon:sum ($order/item,saxon:expression ('@price
Image   * @quantity'))" />
41:   </xsl:template>
42: </xsl:stylesheet>

ANALYSIS

The first 10 lines of Listing 19.5 are the same as those in Listing 19.3. These lines declare the necessary extension namespaces and so on. The root element of the source document is matched by the template on line 12. This template calls the displayorder template, which creates the output for all the orders and the total. This output is created from the order parameter passed on line 14. Note that instead of creating a variable and passing it to the template in a subsequent template call, an xsl:apply-templates element is nested in the xsl:with-param element to give the parameter its value. Using this approach, you don’t need to create an additional variable because the contents of the parameter are created directly. The resulting parameter looks like Listing 19.6. What happens in the displayorder template should by now be familiar to you, except for line 40, which calculates the total using the saxon:sum () function. This function has two arguments: one passing the node-set that needs to be aggregated and one with an expression that defines what the processor needs to do with each node before it is added to the aggregate. The second argument uses the saxon:expression () function to define the expression to be used. In this case, the expression tells the processor that what it needs to aggregate for each node is the quantity attribute multiplied by the price attribute.

LISTING 19.6 Value of the Parameter Passed to the displayorder Template in Listing 19.5

<item price="9.95" id="2" quantity="1">Jumbo Prawns</item>
<item price="10.95" id="3" quantity="2">Smoked Salmon and Avocado
Image   Quesadilla</item>
<item price="6.95" id="4" quantity="2">Caesar Salad</item>
<item price="19.95" id="5" quantity="3">Grilled Salmon</item>
<item price="16.95" id="7" quantity="2">Linguini al Pesto</item>
<item price="6.95" id="10" quantity="1">Dame Blanche</item>
<item price="6.95" id="12" quantity="4">Banana Split</item>

Because of the way the saxon:sum () function works, the expression passed to it can have bearing only on the node-set itself and nodes that are directly addressable from each node. So, you can’t use an expression such as

@quantity * $dishes[@id = current ()/@id]/@price

The current () function doesn’t work inside the expression because the expression created is more or less static. The current () function would therefore be evaluated once, when the expression is created. It is not evaluated for each node of the node-set, so the result is always the same. To get around this problem, Listing 19.5 first creates a parameter where both the price and quantity attributes are included so that they can be addressed through the static expression Saxon creates.

Using Extensions with Other Processors

In the preceding sections, you learned how to use extension elements and extension functions with the Saxon processor. With other processors, the procedure is similar. The main differences lie in how the namespace needs to be declared. Also, each processor has different extension functions and elements. Those supported by Xalan, for instance, differ a great deal from those offered by Saxon. MSXML offers only one extension element, msxsl:script, and one extension function, msxml:node-set (), which converts a tree fragment to a node-set with a single node. This function exists in Xalan and Saxon as well, although its name is slightly different in Saxon.

Creating Your Own Extension Functions

In the preceding section, you learned how to use built-in extensions. Even if the processor supports extensions, chances are they don’t do what you want. For instance, if you need to calculate the square root of a number, you’re out of luck because no extension function performs this task. You can create your own extension functions, however, either by using a script embedded in a stylesheet or by referencing functionality that lies outside the processor.

Using Java Functions as Extension Functions

Java-based processors such as Saxon, Xalan, xt, and Oracle XSL enable you to reference Java classes and use their functionality. This capability is immensely powerful because you can perform functions on the data in the source document and also perform functions with the data from the source document—for instance, sending e-mail or updating a database. You can compare this functionality with browser plug-ins that enable you to view content that you otherwise can’t or to use some interactive medium. In essence, this functionality enables you to create data-driven applications centered around XML data and the XSLT programming language.

Note

From MSXML, you can’t reference Java functions. The only way to use functionality that lies outside the processor is through code in the msxsl:script extension element. This topic will be discussed later in this lesson.

Using Java classes and functions from Saxon or Xalan is remarkably easy. You have to declare a namespace that incorporates the Java class that you want to use. For instance, if you want to use the full range of mathematical functions provided by Java, you can declare the following namespace:

xmlns:math="java.lang.Math"

This references the java.lang.Math class, so you can use the functions available in that class. You should also incorporate the extension-element-prefixes and exclude-result-prefixes attributes to deal with this namespace properly.

Note

According to the Xalan documentation, the preceding namespace declaration is not the preferred method. Instead, you should use xalan://java. lang.Math as the namespace name, after including a special xalan namespace declaration. Both Xalan and Saxon use the class name specified after the last slash, however, and ignore everything that precedes it. Using the preceding declaration is therefore the simplest way to ensure that both Saxon and Xalan can run the same stylesheet.

LISTING 19.7 Sample XML with Numbers to Be Processed

Listing 19.7 shows a simple sample document with some numbers that you can use with some mathematical functions.

<?xml version=″“1.0”" encoding="UTF-8"?>
<numbers>
    <number>2</number>
    <number>9</number>
    <number>144</number>
    <number>65536</number>
    <number>123456789</number>
</numbers>

The simple stylesheet in Listing 19.8 demonstrates the use of a Java class as a processor extension. From Listing 19.7, it creates a list that tells you the square root of each number.

LISTING 19.8 Stylesheet Using Java Function

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:    xmlns:math="java.lang.Math"
5:    extension-element-prefixes="math"
6:    exclude-result-prefixes="math">
7:
8:    <xsl:output method="text" encoding="utf-8" />
9:
10:   <xsl:template match="/">
11:      <xsl:for-each select="numbers/number">
12:       <xsl:text>The square root of </xsl:text>
13:       <xsl:value-of select="." />
14:       <xsl:text> is </xsl:text>
15:       <xsl:value-of select="math:sqrt (.)" />
16:       <xsl:text>.&#xA;</xsl:text>
17:     </xsl:for-each>
18:   </xsl:template>
19: </xsl:stylesheet>

ANALYSIS

Listing 19.8 calculates the square root for each number in Listing 19.7. To do so, the stylesheet uses the sqrt () function that is part of the java.lang.Math class. A reference to this library is created on line 4, which declares the math namespace. Lines 5 and 6 make sure that this namespace is seen as a processor extension and that this namespace is excluded from any output. The stylesheet contains only one template, which iterates through the numbers in Listing 19.7 on line 11. Line 15 is the most interesting of each iteration; the rest is just formatting. Line 15 uses math:sqrt () to calculate the square root of the current number. Listing 19.9 shows the result of Listing 19.8.

OUTPUT

LISTING 19.9 Result from Applying Listing 19.8 to Listing 19.7 in Saxon

The square root of 2 is 1.4142135623730951.
The square root of 9 is 3.
The square root of 144 is 12.
The square root of 65536 is 256.
The square root of 123456789 is 11111.111060555555.

The preceding example shows that using Java classes as extensions is easy. The example, however, is a little deceiving because it works with a simple data type. The situation becomes a little more complex when the argument sent to a class is a node-set or tree fragment. Unless the Java class can figure out how to process the value, chances are the class will not do the job it should do. In fact, it will probably fail, without telling you what went wrong, because the result is just a big blank.

A nice feature of the Java extension mechanism is that when the value passed to the class is a node-set, it can be accessed as a nodelist in the Document Object Model (DOM). In addition, you can write your own Java classes and use them as extensions. Because the data conforms to the XML DOM, accessing the individual values in the node-set is relatively painless.

Note

Demonstrating how to create your own Java class and use it as an extension is beyond the scope of this book because this procedure requires experience with both Java and DOM.

Creating an Extension Function with Script

One of the most exciting features in XSLT is the capability to create your own extensions using script embedded in a stylesheet. As I said before, this procedure currently is implemented with processor extension elements, but this will likely change, at which point this feature will be platform and processor independent.

The concept is simple. You create a script element that holds ECMAScript, Java, or JavaScript code. This code can be called more or less like an extension function of the processor or a Java class. The downside to both of those mechanisms is that your processor needs to support the chosen language. In the case of Java classes, this means that the Java runtime and classes must be available to the processor. With the script embedded in the stylesheet, Java support is not necessary, but the processor must be able to run the script code. The processor either needs to have an embedded script processor or needs to call an external script processor.

The script functions you create can access the data of the arguments through the DOM, the same as the Java extensions discussed in the preceding section.

The best implementation of this feature is in the MSXML component. With Saxon, this element currently works only with external scripts, and with Xalan, this feature is more complex. In addition, Saxon’s documentation doesn’t give you much information to go on. Both Saxon and Xalan clearly favor the approach of writing extension functions as external Java classes, as discussed in the preceding section. Because MSXML doesn’t support Java, it relies on the msxsl:script element to extend the processor’s functionality with script. From the script, you can call COM components, which serve the same purpose as Java classes. Windows contains many COM components, but you can also create them yourself by using languages such as Visual C++, Visual Basic, and Delphi. If the Java runtime is installed on Windows, you can also register Java classes as COM components. Be aware that COM components are native to Windows, however, so they can’t be used on other platforms. Some tests indicate that MSXML script extensions are up to 30 times faster than Java extensions in Saxon or Xalan. Once a new XSLT standard supports a platform-independent extension method, such as embedded script, this will be the preferred solution because it will work on any platform and processor. Java extensions, on the other hand, require Java-based or Java-enabled processors, and COM-based extensions will work only on Windows. The performance versus portability aspect of XSLT extensions in Java and COM is shown in Figure 19.1

FIGURE 19.1 Portability versus performance with XSLT extensions.

Image

Figure 19.1 shows you that pure XSLT performs better than XSLT with extensions of any kind. This is logical because the XSLT processor is specifically designed to perform XSLT transformations as quickly as possible. Pure XSLT is also the most portable, although still not 100% portable, because of processor differences. COM extensions perform very well but are restricted to the Windows platform and therefore not very portable. Java extensions, on the other hand, work on any platform/processor that supports Java. Java extensions do not perform well, however.

When you use the msxsl:script element, you need to tell the processor which language is used to implement the functions with the language attribute. For MSXSL, this is any valid Active Scripting Language, which is any scripting language that is available for the Windows Scripting Host. JavaScript and VBScript are available by default, but other languages are available from third parties, such as Perl from

http://www.activestate.com/. You can find more information about Microsoft scripting language support at http://msdn.microsoft.com/scripting. Besides defining the language used, you also need to tell the processor which namespace these functions will be part of. You can use any namespace you like; the namespace name doesn’t have to point to any specific location. You attach this namespace to the functions inside the msxsl:script element with the implements-prefix attribute, which can contain only one namespace prefix. So, a proper msxsl:script element looks like this:

<msxsl:script language="javascript" implements-prefix=”user">

This line defines an msxsl:script element that uses JavaScript to implement the functions. The functions are available under the user namespace prefix.

The sample document in Listing 19.10 is based on the match calculation example in yesterday’s lesson.

LISTING 19.10 Sample XML with Different Length Strings

<?xml version=″“1.0”" encoding="UTF-8"?>
<teams>
  <team id="1">Nowhere Losers</team>
  <team id="2">Everywhere Winners</team>
  <team id="3">Somewhere Scorers</team>
  <team id="4">Anywhere Flyers</team>
</teams>

ANALYSIS

Listing 19.10 shows several teams. The team names are of interest for this example. Listing 18.15 in yesterday’s lesson contained two templates to fill out the team names, so the number columns would line up properly. Listing 19.11 does the same thing using embedded script functions.

LISTING 19.11 Stylesheet with Embedded Script Extension Functions

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:        xmlns:msxsl="urn:schemas-microsoft-com:xslt"
5:        xmlns:user="http://www.aspnl.com/xmlns/extensions">
6:
7:    <msxsl:script language="javascript" implements-prefix="user">
8:        function maxlen (nodelist) {
9:          var len = 0;
10:         for  (var i=1; i &lt; nodelist.length; i++) {
11:           var s = new String (nodelist.nextNode ().text);
12:           if (len &lt; s.length) {
13:             len = s.length;
14:           }
15:         }
16:         return len;
17:       }
18:
19:       function inschars (num,ch) {
20:         var s = '';
21:         for  (var i=0; i &lt; num; i++) {
22:           s = s + ch;
23:        }
24:        return s;
25:      }
26:   </msxsl:script>
27:
28:   <xsl:output method="text" encoding="utf-8" />
29:
30:   <xsl:template match="/">
31:     <xsl:variable name="maxlen" select="user:maxlen (/teams/team)" />
32:     <xsl:for-each select="/teams/team">
33:       <xsl:value-of select="." />
34:       <xsl:value-of select="user:inschars ($maxlen -
   string-length (.),'-')" />
35:       <xsl:text> </xsl:text>
36:       <xsl:value-of select="$maxlen - string-length (.)" />
37:       <xsl:text>&#xA;</xsl:text>
38:     </xsl:for-each>
39:   </xsl:template>
40: </xsl:stylesheet>

ANALYSIS

Listing 19.11 uses extension functions, so the namespaces that they are part of need to be declared properly. The msxsl:script element is used on line 7 to create the extension functions, so that namespace needs to be declared first, which is done on line 4. Line 5 declares a namespace with the prefix user, which is the namespace of the functions created in the script. Line 7 tells you that the functions implemented are for this namespace and that the language used is JavaScript. The function maxlen on line 8 returns the length of the largest string in a node-set. The argument needs to be a node-set, or the function will fail. Line 9 sets the return value to 0, and line 10 creates a loop to iterate through all the nodes in the node-set. Note that the number of iterations is determined from the length property of the nodelist argument. This argument is a DOM nodelist and is fully accessible with DOM functionality. This is visible again on line 11, which moves to the next node in the node-set and places the string value in the s variable. Line 12 checks whether the length of this string is longer than the current maximum length, and if so, line 13 changes it. Note that the length property here is part of the string object and has nothing to do with DOM functionality. Finally, line 16 returns the length of the longest string.

The function inschars on line 19 is a variation of the insertspaces template in Listing 18.15. This function, which inserts the character that is passed to it a given number of times, has no DOM functionality because all the arguments are simple types. The function simply defines an empty string, to which each iteration on line 21 adds the given character.

The template on line 30 gets the length of the longest team name on line 31, iterates through the team names on line 32, outputs the team name, and then inserts the hyphen character to fill out the team name. The result is shown in Listing 19.12.

OUTPUT

LISTING 19.12 Result from Applying Listing 19.11 to Listing 19.10 in MSXSL

Nowhere Losers---- 4
Everywhere Winners 0
Somewhere Scorers- 1
Anywhere Flyers--- 3

ANALYSIS

Listing 19.12 shows that the inschars function in Listing 19.11 inserts hyphen characters if the team name is shorter than the longest one. The space is inserted by line 35 of Listing 19.11.

Summary

In today’s lesson, you learned that XSLT is extensible. This feature enables users and vendors to add functionality to XSLT to make difficult tasks easy and impossible tasks possible. To distinguish the additional functionality from native XSLT functionality, the added elements, attributes, or functions are part of a separate namespace. Most processors offer a number of extension elements and functions that you can use in a stylesheet.

Because the functions are provided by the different processors, a stylesheet using these functions is no longer processor independent and possibly no longer platform independent.

Many Java-based processors enable you to call functions from Java classes through the extension mechanism. This capability is very powerful because you can create your own Java classes and call them from within the stylesheet. These functions can perform simple tasks on the data or, for example, send an e-mail or update a database. MSXML exhibits this same power through script that is embedded in the stylesheet, which can call COM components that are available on the system, or which you have created with a COM-enabled programming language.

A feature that will be available in future versions of XSLT enables you to create your own processor-independent extension functions. Using ECMAScript, Java, or JavaScript embedded in the stylesheet, you can perform operations that are not available in XSLT itself. XSLT 1.0 doesn’t offer this capability, but the major processors all have an extension element with similar capabilities.

In tomorrow’s lesson, you will learn more details about the differences between the different processors and how you can deal with these differences—for instance, how to deal with different processor extensions.

Q&A

Q If I create an extension function, do I have access to the entire document and all the variables?

A No. An extension function has access only to the data that is passed to it. Any data you want to get needs to be addressable from that data.

Q Can extension elements or functions have side effects?

A Unfortunately, yes. Nothing guarantees that they are free from side effects. In fact, the saxon:assign element is an example of an element that, from an XSLT perspective, has side effects because it makes the result nondeterministic.

Q Do all processors support extension elements and functions?

A Not necessarily. Supporting extension elements and functions isn’t really a requirement, so some vendors may elect not to add extensions and the capabilities to add them yourself. One extension function that is implemented by most processors is a function that converts a tree fragment to a node-set. As explained in yesterday’s lesson, that action is not possible in XSLT 1.0. In XSLT 1.1, it is possible, but most processors will not implement XSLT 1.1 because it is not likely to ever become a W3C Recommendation.

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: Extension elements are processor independent.

2. True or False: The extension-element-prefixes attribute is required when you want to use extension functions.

3. What do you need to do to use functions in a Java class from a Java-based or Java-enabled processor?

4. In what situation can’t you use extensions?

5. Why would you want to create a function embedded in a stylesheet when that same function is available as an extension function for the processor?

Exercises

1. Create a stylesheet for Saxon that uses the saxon:max () function to determine the largest number in Listing 19.7.

Hint: The argument of the saxon:max () function should be a node-set containing the numbers of which you want the maximum number.

2. Create a stylesheet from MSXML that uses a max () function that you define within the stylesheet by using the msxsl:script element of the processor.

Hint: The script function has many similarities with the maxlen function in Listing 19.11. If you can’t figure out what the script is supposed to look like, take a sneak peek at the solution and try to solve the rest yourself.

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

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