Tracing XSLT with a Tiny Java Web Server
Originally published on: Sun, 23 Aug 2009 21:26:52 +0000
Writing and debugging XSL transform documents can be a little tricky. Some of the better XSLT editors provide debuggers that allow one to single-step through the transformation process, allowing one to examine nodes and XSLT variables along the way.
What if an XSLT debugger isn't available? The XSLT document() function may provide a primitive ability to trace through an XSL transform process.
The XSLT document() function is supposed to allow a stylesheet to retrieve a node-set based on a URI specified as the sole argument. Most XSLT processors allow a full HTTP URL to appear in this argument.
In my post Generating Primes with XSLT, I provided a small command-line XSL transform utility written in C# and an XSLT sheet that would display the primes from 2 to 1000 inclusive.
The XLST utility ( xsl.exe ) will not process an XSLT sheet containing a call to the document() function unless a special attribute is set when calling the Load() method in the C# code.
The modified xsl.cs program is as follows:
xsl.cs
// xsl.cs - Simple command-line XSL transform utility
//
// 8/23/2009 - Modified to allow use of the document() function
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
using System ;
using System.IO ;
using System.Xml ;
using System.Xml.Xsl ;
using System.Xml.XPath ;
namespace XslTransform
{
public class XslTransform
{
public static void Main(string[] args)
{
if (args.Length == 2){
XPathDocument dummyXML = new XPathDocument(args[0]) ;
XsltSettings settings = new XsltSettings();
settings.EnableDocumentFunction=true;
XslCompiledTransform myXslTrans = new XslCompiledTransform() ;
myXslTrans.Load(args[1],settings,null) ;
myXslTrans.Transform(dummyXML,null, Console.Out);
}else{
Console.WriteLine("Usage: xsl.exe xml-file xsl-file");
}
}
}
}
Note the lines:
XsltSettings settings = new XsltSettings();
settings.EnableDocumentFunction=true;
XslCompiledTransform myXslTrans = new XslCompiledTransform() ;
myXslTrans.Load(args[1],settings,null) ;
I had to first create an XsltSettings object and set its EnableDocumentFunction attribute to true. Then, I had to call an overloaded version of Load() which took the XsltSettings object as a second parameter. The last parameter is supposed to be used to further control which sites the document() function can query. I placed a null value as that last parameter with no ill effects.
So, now we can invoke an XSL transform that will cause an HTTP GET to occur on a specified web page. How does this help us trace through the execution of a transform? If the HTTP GET queries a web server, that web server will usually provide a log of each GET request.
It may be overkill to stand up an entire web server just to act as a logging mechanism, so let's build our own specialized web server for just that task. Let's start with a simple model web server in Java:
MicroHttp1.java
// MicroHttp1
// A small , specialized web server in Java
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
import java.io.*;
import java.net.*;
import java.util.Date;
public class MicroHttp1 {
public static void main(String[] args)
throws IOException {
ServerSocket serv;
Socket s;
String str;
PrintWriter pw;
BufferedReader br;
serv=new ServerSocket(80);
System.out.println("Micro HTTP Server v 0.1");
System.out.println();
for(;;){
s=serv.accept();
br=new BufferedReader(
new InputStreamReader(
s.getInputStream()));
for(;;) {
str=br.readLine();
System.out.println(str);
if( ! br.ready())
break;
}
System.out.println();
pw=new PrintWriter(
s.getOutputStream(), true);
pw.print("HTTP 200 OK\r\n");
pw.print("Content-type: text/html\r\n\r\n");
pw.print("<html><head /><body>");
pw.print("Current date/time " + new Date());
pw.print("</body></html>");
pw.close();
}
}
}
Execute the above with a command-line consisting of:
java MicroHttp1
You should be greeted with the prompt:
Micro HTTP Server v 0.1
Now, with MicroHttp1 still running, paste the address http://localhost/ into a web browser. You'll likely see text that looks something like:
Current date/time Sun Aug 23 15:54:46 CDT 2009
The tiny web server above simply outputs a short string ending with the current date and time.
The MicroHttp1 console window itself now might be displaying something that looks similar to:
GET / HTTP/1.0
Host: localhost
User-Agent: xxxx
Accept:text/xml,application/xml,application/xhtml+xml,text/html;...etc.
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
We see the GET request with some headers that might be found in most browsers.
What we're now going to do is change MicroHttp1.java into a new version of the web server that output an empty XML element. We will then use a value-of XSL command to embed that value somewhere in the transform. If we embed an empty element ... nothing is added to the resulting document, but the web server will experience a GET request.
Here's a little web server that always outputs an XML document with a sole, empty element named x. I've also changed the console output so that only the GET request is shown. The additional request headers do not display on the console.
MicroHttp2.java
// MicroHttp2
// A small , specialized web server in Java that will
// yield a XML document with an empty element named "x"
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
import java.io.*;
import java.net.*;
import java.util.Date;
public class MicroHttp2 {
public static void main(String[] args)
throws IOException {
ServerSocket serv;
Socket s;
String str;
PrintWriter pw;
BufferedReader br;
serv=new ServerSocket(80);
System.out.println("Micro HTTP Server v 0.2");
System.out.println();
for(;;){
s=serv.accept();
br=new BufferedReader(
new InputStreamReader(
s.getInputStream()));
// Only display the GET line
str=br.readLine();
System.out.println(str);
for(;;) {
if( ! br.ready())
break;
str=br.readLine();
}
System.out.println();
pw=new PrintWriter(
s.getOutputStream(), true);
pw.print("HTTP/1.0 200 OK\r\n");
// Change the payload to contain an
// empty XML element named "x"
pw.print("Content-type: text/xml\r\n\r\n");
pw.print("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
pw.print("<x />");
pw.close();
}
}
}
If you're still running MicroHttp1, stop the execution and run MicroHttp2.
If you reload http://localhost/ in a browser now, you should see an empty XML document with a possible warning about a missing stylesheet.
Now, we're ready to try to perform some trace output in a transform. I've modified the primes.xsl document I'd mentioned at the beginning of this post so that it now includes the following:
<xsl:if test="$val <= 1000">
<xsl:value-of select="document(concat('http://localhost/?',$val))" />
By embedding a value-of command, at each iteration of the recursive, loop, I attempt to hit the web server at http://localhost/. Note that since GET is an idempotent HTTP verb, the web client may decide to cache a value associated with a request that it has already seen. So, in order for the document() call to work at every iteration, I concatenated a question-mark and the value of the variable $val onto the end of the URL.
The entire transform reads as follows:
primestrace.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
Show primes all from 2 to 1000 with
tracing output to a local web server
License: MIT / X11
Copyright (c) 2009 by James K. Lawless
jimbo@radiks.net http:www.radiks.net/~jimbo
http:www.mailsend-online.com
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-->
<xsl:template match="/">
<xsl:call-template name="loop">
<xsl:with-param name="val" select="2" />
</xsl:call-template>
</xsl:template>
<xsl:template name="loop">
<xsl:param name="val" select="''" />
<xsl:if test="$val <= 1000">
<xsl:value-of select="document(concat('http://localhost/?',$val))" />
<xsl:call-template name="findPrime">
<xsl:with-param name="start" select="2" />
<xsl:with-param name="end" select="$val" />
</xsl:call-template>
<xsl:call-template name="loop">
<xsl:with-param name="val" select="$val+1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="findPrime">
<xsl:param name="start" select="''" />
<xsl:param name="end" select="''" />
<xsl:if test="($start * $start) > $end">
<xsl:value-of select="concat($end,' is prime ')" />
</xsl:if>
<xsl:if test="($start * $start) <= $end">
<xsl:if test="($end mod $start)!=0">
<xsl:call-template name="findPrime">
<xsl:with-param name="start" select="$start + 1" />
<xsl:with-param name="end" select="$end" />
</xsl:call-template >
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The trailing output in the MicroHttp2 window should look something like:
GET /?995 HTTP/1.1
GET /?996 HTTP/1.1
GET /?997 HTTP/1.1
GET /?998 HTTP/1.1
GET /?999 HTTP/1.1
GET /?1000 HTTP/1.1
After all of that set up, I have a small web server (MicroHttp2) that will parrot the GET request to the console. I can use the XSLT document() function to build a unique request with tracing information and can pepper calls to this function throughout a transform stylesheet. The results should appear on the MicroHttp2 console window.
Unless otherwise noted, all code and text entries are Copyright ©2009 by James K. Lawless
Save to del.icio.us
Save to StumbleUpon
Digg it
Save to Reddit
Share on Facebook
Share on Twitter
More bookmarks