devxlogo

Creating Generic XSLT Transforms

Creating Generic XSLT Transforms

uch of the focus of XSLT has been on its “stylesheet” capabilities?the ability to convert XML into some form of displayed HTML. However, XSLT is a functional language, one that takes an XML “state” and converts it into a different XML state?in essence, a form of black box that takes data in and spits different data out. You can use this to your advantage to turn XSLT scripts into client (or server side) methods that add significant power to your web development toolset, especially if your clients use Internet Explorer.



XSLT stylesheets are powerful and relatively easy to create for transforming specific XML documents, but it’s not so apparent that they can also be generic, like a function call, so that they can handle a wide range of XML documents.



Parameters are an underused aspect of XSLT, but XSLT parameters can turn a specialized transformation script into a generalized method call. While an XML document contains the data on which the transformation will work, the data by itself is often not quite enough to accomplish a task. By passing parameters to your XSLT documents, you can supply the extra information and simultaneously create transformations usable with many different XML documents.

Basic Transformations
To clarify this, consider a simple XML document that consists of basic records (see Listing 1).

Suppose that you wanted to create a simple list that displayed the contents of the XML document shown in Listing 1. Listing 2 contains a very simple stylesheet to do just that. The stylesheet iterates through the records in the document, putting each in it’s own

element, that contains the record id and the first and last names from each record .

Although the the sample displays the records in document order, the sample document records are ordered by user ID, simply because as each record is added to this list, the new user ID assigned is probably greater than the previous one. However, in many cases, a user ID provides very little ordering information; if the IDs were not generated sequentially but through some kind of algorithmic hash (or if the user ID were selected by the user), then the IDs would not be ordered. In other words, it would be preferable to be able to sort this information by some other key.

You could, of course, create one stylesheet for each kind of sorting. For example, the stylesheet in Listing 3 will sort automatically by first name:

Parameterized Transformations
Unfortunately, even with something as simple as the record set given above, you would need six different stylesheets to describe all the possible permutations — firstname ascending, firstname descending, lastname ascending, ID ascending, and so forth. In other words, it makes more sense to attempt to parameterize the stylesheet, in this case with two parameters: $sortKey and $sortOrder. The $sortKey parameter specifies the name of the element or attribute to use as the sorting expression, while the $sortOrder parameter contains one of the values “ascending” or “descending”.

The stylesheet in Listing 4 defines the two parameters $sortOrder and $sortKey at the global level?that is, outside of any specific template. Therefore, the two parameters are available to all the templates in the stylesheet. You can then sort using a single XSLT command:

   

The select attribute performs a generalized search, looking for either elements (“*[name(.)”) or attributes (“@*[name(.)”) that have the name of the sort key. By checking both, you can easily sort on attributes as well as elements, minimizing the need to have two such routines. You must escape the order attribute using the {} brackets, because its content is explicitly defined to be a string (in fact, it’s a Name Token, but at least with most XSLT parsers, tokens are typically mapped to the string type).

is to convert six (and conceivably many more, if you were dealing with real records) stylesheets into one and turn a static stylesheet into a dynamic function that not only displays the records set but also uses the two parameters to control how that record set displays. You can use this subtle point to your advantage by encapsulating transformations in procedural functions.

Wrapping XSLT in Procedural Clothes
An XML Data Island is a Microsoft-specific way to include XML content within HTML pages in Internet Explorer. A data island has an tag with optional id and src attributes. While you can use data islands to provide local XML for data population operations, you can also use these islands to store your XSLT code. For example, you can create a data island consisting of the XML code in Listing 1 as follows:

                                    Kurt               Cagle                           

Similarly, you can store the XSLT as:

                           

You can then retrieve an XML DOM version of the data island with the XMLDocument property of the element. For example, to retrieve the XSL document as a DOM element using JavaScript, you would write:

   var xslDoc= _ShowRecords.XMLDocument;

I added the underscore to the ID to indicate that this is a data island function as opposed to a simple transform.

XML documents created in this way are for the most part indistinguishable from xml documents loaded from a file, though with inline data islands you can’t update the island’s XML contents back to the island itself (usually not a major limitation). You can create a data island as inline code as shown above. In addition, you can reference XML (and XSL) files on the server through the src attribute. For example, if the transformation shown in Listing 4 is available on the server as the relative URL ShowRecords.xsl, then you can use the src attribute to create a reference to it from an empty data island:

   

After you have the XML and XSLT documents in data islands, you can pass the XML to the stylesheet using some basic JavaScript. However, rather than writing a function that calls the stylesheet internally, you can pass the stylesheet itself as an argument to a generalized function, as well as passing additional parameters. I did this by creating a generic call() function in JavaScript (see Listing 5):

   function call(xslIsland,xmlIsland){      var xslDoc = new        ActiveXObject       ("MSXML2.FreeThreadedDOMDocument");      var rsltDoc = new             ActiveXObject          ("MSXML2.FreeThreadedDOMDocument");      var xslTemplate = new ActiveXObject          ("MSXML2.XSLTemplate");      xslDoc.load(xslIsland.XMLDocument);      xslTemplate.stylesheet=xslDoc;      var xslProc=xslTemplate.createProcessor();      xslProc.input=xmlIsland.XMLDocument;      xslProc.output=rsltDoc;      if (arguments.length > 2 &&          arguments.length % 2 == 0){         for (var i = 0;             i < Math.floor            ((arguments.length) / 2) - 1;             i++) {                paramName=arguments[2*i+2];              paramValue=arguments[2*i+3];              xslProc.addParameter                (paramName,paramValue);            }         }      xslProc.transform();      return rsltDoc;   }

The call() function takes two primary arguments — an XML data island reference and an XSL Data Island reference. While I could have passed both XML and XSL as DOMs, passing them as data island references de-emphasizes their roles as files and instead makes the function act more like a method. The following example shows how to invoke the call() function to display the records by first name in ascending order:

   call(_ShowRecords,records,"sortKey","firstname",      "sortOrder","ascending");

This technique encapsulates the XSL processing within a transparent process, with a very straightforward call syntax. Note, however, that a few parameters snuck into the call() function invocation. The function takes advantage of a JavaScript capability that VBScript doesn’t support?the ability to create virtual parameters. The first two parameters specify the data on which the method works. Each succeeding pair of parameters contains a name and value that the call() function will in turn pass to the XSLT function as parameters. Thus, the first parameter is named “sortKey” with a value of “firstname”, the second parameter is named “sortOrder” with a value of “ascending”. This was specifically done with the arguments JavaScript collection:

      if (arguments.length > 2 &&          arguments.length % 2 == 0){         for (var i=0;            i < Math.floor((arguments.length)/2)-1;            i++){            paramName=arguments[2*i+2];            paramValue=arguments[2*i+3];            xslProc.addParameter(paramName,paramValue);         }      }

The routine iterates over all arguments after the first two, retrieves the parameter name and parameter value (the parameters must be in that order), and then uses the XSL Processor object (part of the MSXML3 and MSXML4 library) to set the specified XSLT parameter name to the specified value.

By doing this you can make the call() function completely generic — regardless of the data, the transformation, or the parameters that are involved in the transformation. It also means that you can define a number of XSLT “methods” in your page, then invoke them through the call() function. To simplify things somewhat for this case, I also wrapped this call() function for the particular case in another function, showRecords():

   function showRecords(sortKey,sortOrder){      var resultDoc =          call(_ShowRecords,records,"sortKey",          sortKey,"sortOrder",sortOrder);      window.container.innerHTML=resultDoc.xml;   }

The showRecords() function uses the call() function internally to retrieve an XML DOM object that’s placed in the variable resultDoc. The showRecords() function obtains the resulting XML string using the resultDoc.xml property and places the contents in the

element with the id “container,” where it displays as HTML.

Download the code for the entire encapsulated.htm file. The user interface lets you select different views of the data table.

Into the Production World
While the example used in this article is fairly simple, a full web site can make use of the same techniques to define a general API for calling XSLT methods. For example, you can create XSLT methods to generate dynamic menus based upon XML resource files, form display based on XML record objects, article formatters based upon XML raw source and XSLT display engines, and so forth. Additionally, you can retrieve query string arguments and use them as parameters into more sophisticated methods that maintain state–an issue that also comes into play with clients because clients, unlike servers, generally are well optimized for keeping state information intact.

I’ll explore this topic in more detail in future articles, where I hope to provide both client and server method call architectures (also known as web services, though only the most general sense). The combination of intelligent clients and intelligent servers using XSLT to shape the XML passing between them effectively changes the dynamic of web programming, making it both more flexible and easier to develop, not always an achievable goal.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist