Jim Lawless' Blog


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



Views expressed in this blog are those of the author and do not necessary reflect those of the author's employer. Views expressed in the comments are those of the responding individual.

stumbleupon Save to StumbleUpon
digg Digg it
reddit Save to Reddit
facebook Share on Facebook
twitter Share on Twitter
aolfav More bookmarks


Previous post: A Command-Line CD Tray Opener
Next post:Scrolling GIF Banners with PerlMagick


About Jim ...


Click **here**
to try out MailWrench;
a command-line SMTP /
SMTPS (Google Gmail)
mailer for Windows.


Follow me on Twitter

http://twitter.com/lawlessGuy


Recent Posts

A JavaScript REPL for Android Devices

MailSend is Free

My Blog Engine

The October 10th Bug

A Review of Kevin Mitnick's Book Ghost in the Wires

Spellbound by Web Programming

Backlinks to my Blog Posts

Play MP3 Files with Python on Windows


Random Posts

Twimmando: A Command-line Twitter Client

Switching a Console Window to Full Screen or Windowed Mode

A Simple Media Control Interface Script Processor

Java in a Windows EXE with launch4j

An Interview with the Creator of the BDS C Compiler

Pi Day Meets the HTML5 Canvas

An Interview with Game Developer James Hague

Stacking Images with PerlMagick

Obfuscated C

Hiding Batch File Console Windows


Full List of Posts

http://www.mailsend-online.com/bloglist.htm


Recent Posts from my Other Blog

Remembering Dr. San Guinary

Why Some Web Sites will go Dark on Jan 18th

SNL Superhero Skit

More Ruby Games

My Ruby Game Challenge Entry

Steal this Bookmarklet

Nerd Toys

Learn New Jargon, You Must

Spot the Wiebe

Tech Magazine Glory Days

Book Review : Paull Allen - Idea Man

A 90's Experiment in Online Systems - The U.S. West CommunityLink Service