Pete's Nifty Visual Basic XSLT Tester

By Peter A. Bromberg, Ph.D.

Peter Bromberg

Recently when beginning to develop a series of XSLT include files for a web-based document printing component at the company I work for (my "day" job), I needed to be able to quickly test a transform that was in the process of debugging and testing. Of course there are excellent commercial products such as XML Spy that do this (and you can also just set a reference to the XSL Stylesheet in the XML document itself and load it into IE 5.0 or IE 5.5 and see the transform). But I really needed finer-grained control. Some of my transforms were being output as text (not HTML) for the purpose of populating form templates in Acrobat and Crystal that were to be printed locally by the user from their browser. I needed to see things like whether my script to generate linefeeds and similar functions were working. And especially, since I am passing named parameters and their values into each stylesheet, it gets even more complicated. At first I used an ASP page with either VBScript or Javascript to instantiate my CreateProcessor() transforms, accepting the parameter name and value off the querystring, as such:



    
 
Set xmldoc = Server.CreateObject("Msxml2.FreeThreadedDOMDocument.3.0")
xmldoc.async = false
xmldoc.load(Server.mapPath("TestDoc.xml"))
Set xsldoc =  Server.CreateObject("Msxml2.FreeThreadedDOMDocument.3.0")
xsldoc.async = false
xsldoc.load(Server.mapPath("DocTransform.xsl"))

Set template=Server.CreateObject("MSXML2.XSLTemplate")
template.stylesheet=xsldoc
Set processor=template.createProcessor


processor.input = xmldoc
strVal =   Request.querystring("acctid")
if strVal <> "" then
processor.addParameter "acctid", strVal
end if
processor.transform()
Response.write  (processor.output)

    

But then I realized I could probably throw together a VB app to do this, put in some Common Dialogs for opening and saving the files, and get a lot more functionality out of it. "Pete's Nifty XSL Transform Tester" is the result. Not only can you load and save your XML and XSL files, you can pass in an optional parameter name and value, and you can make minor edits and, using a right-click context menu, you can save the revised file back to the filesystem. It also uses the parseError properties and methods to give detailed error messages. Let's take a look at this simple but extremely useful project and how it works:

Looking at the screen shot above, you see we have a rich text box for the XML, one on the right for the XSL, and a big one on the bottom to view the Transform output. Over on the left we have a "Load XML" button, one to "Load XSL", a checkbox for "HTML output" (as opposed to text output - I'll get to that neat trick in a moment), and finally, a "PROCESS" button to fire off the transform and display the result. We also have two small textboxes to enter a parameter name and value.

The neat thing about the bottom textbox is that its actually two controls, one sitting right on top of the other. One is a regular text box with its multiline property set to True - so we can look at the results of <xsl:output method="text"/> template directives. The second one is a WebBrowser control - exactly the same size and layout. In this manner, when we test for the "HTML Output" checkbox control and it's checked, we simply set the visible property of the textbox to false, the WebBrowser control to true, and write our output to the WebBrowser control. When the "HTML Output" checkbox is empty, we set the visible property of the textbox control to true and write our output there. Pretty cool, eh?

Now, let's step through the code:


Option Explicit
Private m_hMenu As Long
Public sContent As String

Private Sub Form_Load()
    m_hMenu = CreatePopupMenu()
    Call mnuAddItem(m_hMenu, 100, , "Save File")
End Sub
Private Function OpenFile(sFileName) As String
   If Len(sFileName) = 0 Then Exit Function
    Dim fs As New Scripting.FileSystemObject
    Dim f As Scripting.TextStream
    Set f = fs.OpenTextFile(sFileName, ForReading, False)
    OpenFile = f.ReadAll
    f.Close
    Set f = Nothing
 End Function

Private Sub SaveTheFile(sControl As String)
     If sControl = "txtXML" Then
        dlg.Filter = "XML Files (*.xml)|*.xml"
        sContent = txtXML.Text
        dlg.ShowSave
        WriteFile dlg.FileName, sContent
    Else
        dlg.Filter = "XSL Files (*.xsl)|*.xsl"
        sContent = txtXSL.Text
        dlg.ShowSave
        WriteFile dlg.FileName, sContent
    End If
 End Sub
Private Sub WriteFile(sFileNameAndPath As String, sContent As String)
    Dim fs As New Scripting.FileSystemObject
    Dim f As Scripting.TextStream
    If Not sFileNameAndPath = "" Then
    Set f = fs.OpenTextFile(sFileNameAndPath, ForWriting, True, TristateUseDefault)
    f.Write sContent
    f.Close
    MsgBox "File " & sFileNameAndPath & " Saved."
    End If
End Sub

Private Sub cmdLoadXML_Click()
    dlg.Filter = "XML Files (*.xml)|*.xml"
    dlg.ShowOpen
    txtXML.Text = OpenFile(dlg.FileName)
End Sub
Private Sub cmdLoadXSL_Click()
    dlg.Filter = "XSL Files (*.xsl)|*.xsl"
    dlg.ShowOpen
    txtXSL.Text = OpenFile(dlg.FileName)
 End Sub
Private Sub cmdProcess_Click()
    Dim strParamVal As String
    Dim strParamName As String
    Dim myDoc As Object
    Dim xmlDoc As New FreeThreadedDOMDocument30
    Dim xslDoc As New FreeThreadedDOMDocument30
    Dim template As New MSXML2.XSLTemplate30
    Dim processor As MSXML2.IXSLProcessor
    
If (txtXML.Text <> "" And txtXSL.Text <> "") Then
        xslDoc.validateOnParse = True
        xmlDoc.validateOnParse = True
        xslDoc.loadXML txtXSL.Text
        xmlDoc.loadXML txtXML.Text
        Set template.stylesheet = xslDoc
        Set processor = template.createProcessor
        processor.input = xmlDoc
        strParamVal = txtParamValue.Text
        strParamName = txtParamName.Text
        
        If strParamVal <> "" Then
            processor.addParameter strParamName, strParamVal
        End If
     processor.Transform
    If Check1.Value = 1 Then ' HTML Output
       WebBrowser1.Visible = True
       txtResult.Enabled = False
       txtResult.Visible = False
       WebBrowser1.Navigate "about:blank"
       Set myDoc = WebBrowser1.Document
       myDoc.Open
       myDoc.Write processor.output
       myDoc.Close
    Else
       WebBrowser1.Visible = False
       txtResult.Visible = True
       txtResult.Text = processor.output
    End If
End If
  
End Sub

Private Sub txtXML_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
   Dim lngID As Long
   If Button = vbRightButton Then
   lngID = mnuPopMenu(m_hMenu, Me.hwnd, TPM_RETURNCMD)
   If lngID = 0 Then
             MsgBox ("Canceled")
   Else
            If lngID = 100 Then SaveTheFile txtXML.Name
    End If
    End If
    
End Sub

Private Sub txtXSL_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
    
    Dim lngID As Long
    If Button = vbRightButton Then
        lngID = mnuPopMenu(m_hMenu, Me.hwnd, TPM_RETURNCMD)
     
        If lngID = 0 Then
        MsgBox ("Canceled")
        Else
            If lngID = 100 Then SaveTheFile txtXSL.Name
        End If
    End If
End Sub

Basically we load our form, hook CommonDialog "Load" methods into the "LOAD XML" and "LOAD XSL" button events, and check our validateonParse method to make sure the XML and XSL Documents are valid. Then we go ahead and do our transform, using the createProcessor method of the IXSLProcessor object ( Dim template As New MSXML2.XSLTemplate30, Dim processor As MSXML2.IXSLProcessor) Here is the "meat" of the code that actually does any parameter adding and calls the transform:

       Set template.stylesheet = xslDoc
        Set processor = template.createProcessor
        processor.input = xmlDoc
        strParamVal = txtParamValue.Text
        strParamName = txtParamName.Text
        If strParamVal <> "" Then
            processor.addParameter strParamName, strParamVal
        End If
     processor.Transform
Then we check to see if HTML output is checked, and decide where to send the output:
If Check1.Value = 1 Then ' HTML Output 
WebBrowser1.Visible = True
txtResult.Enabled = False
txtResult.Visible = False
WebBrowser1.Navigate "about:blank"
Set myDoc = WebBrowser1.Document
myDoc.Open
myDoc.Write processor.output
myDoc.Close
Else WebBrowser1.Visible = False
txtResult.Visible = True
txtResult.Text = processor.output End If

The rest of the code simply provides a hook into the content menu item that allows a user to make a change to either document, and save it back to the filesystem, calling the save routine:

Private Sub WriteFile(sFileNameAndPath As String, sContent As String)
Dim fs As New Scripting.FileSystemObject
Dim f As Scripting.TextStream
If Not sFileNameAndPath = "" Then
Set f = fs.OpenTextFile(sFileNameAndPath, ForWriting, True, TristateUseDefault)
f.Write sContent
f.Close
MsgBox "File " & sFileNameAndPath & " Saved."
End If
End Sub

The code for the Contents menu is in modCBMenuHelpers, which is included with the source code download. This is pretty simple stuff, but it sure has saved me a lot of time. I am sure you can think of some great ways to enhance this and add new features. Hope you enjoy "Pete's Nifty VB XSLT Tester!

Download the code for this article

Peter Bromberg is an independent consultant specializing in distributed .NET solutionsa Senior Programmer /Analyst at in Orlando and a co-developer of the NullSkull.com developer website. He can be reached at info@eggheadcafe.com