- Extending the Basic Style Sheet
- Define the Functionality
- Summary
Define the Functionality
Defining the functionality also takes advantage of namespaces. Just as we used namespaces to let the processor know which elements and functions are part of the extension, we use them to indicate the elements that define the new functionality, as shown in Listing 6.
Listing 6Namespace and Extension Information
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:lxslt="http://xml.apache.org/xslt" xmlns:totalSys="TotalSystem" extension-element-prefixes="totalSys" version="1.0"> ... </xsl:stylesheet>
The Xalan processor understands that elements in the http://xml.apache.org/xslt namespace define the functionality of extension functions.
The second addition here, extension-element-prefixes, doesn't actually define any new functionality, but it helps the processor to deal with the extension elements appropriately. Under normal circumstances, elements in the xsl: namespace are taken as functionality, and elements in all other namespaces are transformed. The extension-element-prefixes attribute tells the processor that the totalSys elements should be evaluated as functionality and executed, and not as data and output.
Create the Component
Now that the namespaces are set, it's time to start actually specifying functionality. We'll do this by creating a component, which acts like a container for our scripts, as shown in Listing 7.
Listing 7The Component as a Container
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:lxslt="http://xml.apache.org/xslt" xmlns:totalSys="TotalSystem" extension-element-prefixes="totalSys" version="1.0"> <lxslt:component prefix="totalSys" elements="resetTotal customerInfo" functions="totalCost getExtPrice"> <lxslt:script lang="javascript"> </lxslt:script> </lxslt:component> <xsl:template match="/"> ...
The component acts as a container, but it also provides other important information for the processor. First, the prefix attribute tells the processor that this is the place to look for definitions in the totalSys namespace.
The processor needs the elements and functions attributes to indicate which is which because they are handled differently, as we'll see when we define the scripts.
Extension Function
The two extension functions are perhaps the easiest to understand because they behave just like "normal" JavaScript functions, as shown in Listing 8.
Listing 8Extension Functions
... <lxslt:component prefix="totalSys" elements="resetTotal customerInfo" functions="totalCost getExtPrice"> <lxslt:script lang="javascript"> var orderTotal = 0; function customerInfo (xslContext, elem) { return null; } function resetTotal(xslContext, elem) { return null; } function getExtPrice (qty, unitPrice) { extPrice = parseFloat(qty*unitPrice); orderTotal = parseFloat(orderTotal + extPrice); return extPrice; } function totalCost() { return orderTotal; } </lxslt:script> </lxslt:component> ...
Both functions deal with the orderTotal, which we'll define as a global variable and initialize with a value of 0. The totalCost() function simply returns this value.
The getExtPrice() function takes its arguments, multiplies them together to get the extended price, and updates the orderTotal variable. It then returns the extended price.
These values can then be output to the overall page, as seen in Figure 2.
Figure 2 Functions can output values to the page.
Notice that in addition to the missing customer information, the total for the second order is wrong. We'll take care of both problems with extension elements.
A Simple Extension Element
Let's start with the resetTotal element. Within the body of the style sheet, we specified it as
<totalSys:resetTotal resetTo="0"/>
Notice the use of the resetTo attribute. This attribute allows us to start every order at a certain point. For instance, all orders might carry a shipping and handling charge, or a convenience fee. In this case, we're just resetting each total to 0, but adding that fee would be a simple matter of changing the attribute value.
When the processor encounters the resetTotal element, it executes the resetTotal script, as seen in Listing 9.
Listing 9The resetTotal Script
... <lxslt:component prefix="totalSys" elements="resetTotal customerInfo" functions="totalCost getExtPrice"> <lxslt:script lang="javascript"> var orderTotal = 0; ... function resetTotal(xslContext, elem) { resetTo = elem.getAttribute("resetTo"); orderTotal = resetTo; return null; } ... </lxslt:script> </lxslt:component> ...
Now we get into the difference between extension functions and extension elements. Although extension functions take only the arguments we give them, extension elements take a very specific pair of arguments. The first is the context in effect at the time the element is encountered, and the second is the element itself. We'll deal with the former in a minute; we'll deal with the latter now.
When the processor encounters an extension element, the element itself is passed as an argument to the script. Any attributes that are part of the element can then be accessed using the getAttribute() method, just as they could for any other XML element. In this case, we retrieve the value of the resetTo attribute, and use it to set the value of the orderTotal variable.
With this script in place, the resetTotal element causes the orderTotal value to get zeroed out every time a new order is encountered because of its position in the style sheet:
... <xsl:template match="order"> <p><b>Customer Information: </b><t:customerInfo/></p> <t:resetTotal resetTo="0"/> <table width="50%" border="1"> ... </table> <br /><hr /><br /> </xsl:template> ...
Notice, however, that it doesn't actually output anything to the final result. Because of this, we set the script to return null.
A More Complex Extension Element
Finally, we have the customerInfo extension element. This one takes no custom attributes (although it could), but returns information on the customer whose order is being displayed.
But how, you may wonder, does it know which customer's information to display when it doesn't take any attributes to specify it?
The answer lies in the context of the request.
The results of any XPath expression depend not only on the request itself, but also on the context of the request. For example, the XPath expression
product/price
returns a different set of nodes depending on which order element is the context node at the time.
An extension element, as we saw above, gets the context of the request passed to it as an object. This allows us to retrieve the proper customer information by navigating to it from the context node, as seen in Listing 10.
Listing 10The customerInfo Element
... <lxslt:component prefix="totalSys" elements="resetTotal customerInfo" functions="totalCost getExtPrice"> <lxslt:script lang="javascript"> var orderTotal = 0; function customerInfo (xslContext, elem) { thisOrder = xslContext.getContextNode(); thisCustomer = thisOrder.getAttribute("custid"); custInfo = getCustomerInfo(thisCustomer); return custInfo; } function getCustomerInfo(custID) { ... } ... </lxslt:script> </lxslt:component> ...
The first task here is to retrieve the context node from the xslContext object. This node, thisOrder, is a normal XML node representing the current order element in the source document because that's the context node at the time when the processor encounters the customerInfo element.
It's important to understand that thisOrder represents the actual order element in the source document. Because of this, we can use methods such as getChildNodes() to explore the entire source tree, if we wanted to. In this case, however, all we really want is the value of the custid attribute of the original element, so we'll retrieve it with the getAttribute() method.
In this way, we have access to the proper customer identifier for each order, even though there's nothing in the actual style sheet that points to it.
Once we have the custid, we can feed it to another function that accesses the database. The function itself is beyond the scope of this article, but understand that it returns the customer information for the custid provided. The customerInfo element then returns this information to the page, as seen in Figure 3.
Figure 3 The completed page.