Using EXSLT To Extend XSLT With Custom Functions In ColdFusion

Posted November 11, 2009 at 10:26 AM by Ben Nadel

Tags: ColdFusion

The other day, I answered a question for a reader about filtering XML nodes against a value list using the XPath function, contains(). In the question, the reader mentioned the EXSLT library; I had not heard of this, so when researching the solution, I looked it up at www.exslt.org. As it turns out, there's a whole mechanism and existing library for extending the functionality of XSLT and XPath. And, what's more awesome is that the XSLT engine that ColdFusion uses (Xalan) supports this extensioning.

One of the XSLT extensions offered in the EXSLT library is the "func:function" node. This node allow programmers to define their own XSLT-based custom functions that can be called within their XML transformations. As a launch pad for experimentation with this stuff, I figured I would readdress my previous blog post, but this time, try to solve the problem using EXSLT and a custom function.

If you don't remember from my previous post, the reader needed to filter an XML node by checking its attribute value against a list of target values. To solve this the first time, I hacked the contains() function to work as a list function (mostly); but, using EXSLT, I wanted to try and create an actual list function, list-find(), that would return true if a given value could be found in a given list. Here's what I came up with:

  • <!--- Create our XML data source. --->
  • <cfxml variable="girls">
  •  
  • <girls>
  • <girl type="cute">
  • <name>Sarah</name>
  • </girl>
  • <girl type="athletic">
  • <name>Tricia</name>
  • </girl>
  • <girl type="hot">
  • <name>Katie</name>
  • </girl>
  • <girl type="cute">
  • <name>Libby</name>
  • </girl>
  • </girls>
  •  
  • </cfxml>
  •  
  •  
  • <!--- Create the XSL tranformation. --->
  • <cfsavecontent variable="xslt">
  •  
  • <!--- Document type declaration. --->
  • <?xml version="1.0" encoding="ISO-8859-1"?>
  •  
  • <!---
  • When defining the Transform, you must add the namespace
  • for the EXSL (XSL Extensions) functionality so that they
  • can be properly resolved.
  •  
  • NOTE: I am also adding my own name space, "ben", for use
  • in the function name and information attributes.
  • --->
  • <xsl:transform
  • version="1.0"
  • xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  • xmlns:ben="http://www.bennadel.com/xslt"
  • xmlns:func="http://exslt.org/functions"
  • xmlns:str="http://exslt.org/strings"
  • extension-element-prefixes="func str ben">
  •  
  • <!---
  • The list-find() function allows us to search the
  • given list for the given value. By default, the
  • delimiters is the comma, but you can override with
  • an optional argument.
  • --->
  • <func:function
  • name="ben:list-find"
  • ben:return-type="boolean"
  • ben:hint="I take a delimited list and a value and return true or false as to whether that value is a list item in the given list.">
  •  
  • <!--- Define arguments. --->
  • <xsl:param
  • name="list"
  • ben:type="string"
  • ben:required="true"
  • ben:hint="I am the delimited list of values that is being searched."
  • />
  •  
  • <xsl:param
  • name="value"
  • ben:type="string"
  • ben:required="true"
  • ben:hint="I am the value for which we are searching in the above list."
  • />
  •  
  • <xsl:param
  • name="delimiters"
  • select="','"
  • ben:type="string"
  • ben:required="false"
  • ben:hint="I am the collection of delimiters used to deliniate the list values. I can be one or more characters (each character is used as a valid delimiter). I am defaulted to the comma."
  • />
  •  
  • <!---
  • Using the EXSLT extension string library, split
  • the list based on the given delimiters. This will
  • return a node set of "token" nodes.
  •  
  • Ex: str:tokenize( 'a,b,c', ',' )
  • ...
  • <token>a</token>
  • <token>b</token>
  • <token>c</token>
  • --->
  • <xsl:variable
  • name="tokens"
  • select="str:tokenize( $list, $delimiters )"
  • />
  •  
  • <!---
  • Return a boolean-ized version of a query for token
  • nodes who's text() value is equal to the list item
  • value we are searching.
  •  
  • Boolean() will return TRUE if the resultant node
  • set returned from the select is non-empty.
  • --->
  • <func:result
  • select="boolean( $tokens/text() = $value )"
  • />
  •  
  • </func:function>
  •  
  •  
  • <!--- Match the root girls node. --->
  • <xsl:template match="/girls">
  •  
  • <girls>
  •  
  • <!---
  • Copy each GIRL node if the TYPE attribute
  • is contained within the given list: hot or
  • athletic.
  •  
  • NOTE: We are using a custom function as defined
  • by the EXSLT func library.
  • --->
  • <xsl:copy-of
  • select="./girl[ ben:list-find( 'athletic,hot', @type ) ]"
  • />
  •  
  • </girls>
  •  
  • </xsl:template>
  •  
  • </xsl:transform>
  •  
  • </cfsavecontent>
  •  
  •  
  • <!--- Transform the document. --->
  • <cfset hotGirls = xmlParse(
  • xmlTransform( girls, trim( xslt ) )
  • ) />
  •  
  • <!--- Output the new result. --->
  • <cfdump
  • var="#hotGirls#"
  • label="Hot Girls"
  • />

As you can see, my function "ben:list-find" takes 2 to 3 parameters - the list, the value in question, and an optional set of delimiters. Since this function is not part of the inherent XPath functionality, it needs to be name-spaced. Using the "ben" name space, I not only defined the function tag, I also defined many informational attributes that described the role of the function and its parameters (ala the attributes available in ColdFusion's CFFunction and CFargument tags). You know my style - the more insight, the better.

Running the above code, we get the following XML document:

 
 
 
 
 
 
Using EXSLT To Create Custom User Defined Functions In XSLT For Use In XML Transformation In ColdFusion. 
 
 
 

As you can see, only the girl nodes who's @type attribute was in the list "athletic,hot" were returned in the resulting XML document. This is clearly a much better solution than my contains() approach.

The EXSLT library looks very interesting. What looks even more interesting, though, is that it appears that EXSLT itself has an extension mechanism in which new functions can be written in Javascript! Of course, this requires some additional Java libraries such as Rhino.jar; but, how wild would that be? Writing Javascript functions that could be leveraged in XSLT? But, that's for a whole other blog post.




Reader Comments

Nov 11, 2009 at 6:47 PM // reply »
76 Comments

Nice post Ben, that is a handy feature


Nov 11, 2009 at 9:57 PM // reply »
10,640 Comments

@Shuns,

Yeah, it seems like a cool thing.


Feb 14, 2011 at 1:34 PM // reply »
2 Comments

Is there some secret to using these exslt libraries?

I am trying to use one of their math functions math:power.

When ever I try to use it without a xsl:include line I get an error.

javax.xml.transform.TransformerException: ElemTemplateElement error: math:power
A Transformer object cannot be created that satisfies the configuration requested.

Here is my xsl:stylesheet tag:

My current ColdFusion is 9.0.


Feb 14, 2011 at 2:05 PM // reply »
2 Comments

So I downloaded your hot girls example and it runs fine.

So it seems to be something about the math functions, particularly the math:power function that either ColdFusion or Xalan or both does not like.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »