Menu

VB as Device Controller

July 12, 2000

Kurt Cagle

Of course, if you can control the presentation layer and the business rules using XML, you could legitimately ask what use Visual Basic is. In reality, the role that VB plays is shrinking daily, at least as far as designing interfaces goes. It is still faster than a similar web-based application, has a considerably broader range of tools for integrating components together, and definitely has a superior editing environment. However, the flexibility of web-based applications is making IT managers pause when considering developing client/server applications.

Ironically, VB itself is rapidly making inroads in an area that until recently was exclusively the domain of scripting and complex ISAPI filters written in C++--the domain of the server. C++ is not a terribly good language for doing web development (this should bring in a flood of controversy, but I stand by the statement). The key to server deployment is usually rapid development and component service integration, and while C++ is reasonably good at creating the components in the first place, the fine granularity of the language is definitely overkill for pulling component pieces together.

Server side scripts, on the other hand, suffer from the twin limitations of being interpreted rather than being compiled, and of lacking strong internal organizational tools. Visual Basic, on the other hand, offers the best of both worlds--an easy to use programming language that is quite fast compared to scripts, that tends to force at least object-like if not necessarily object-oriented, programming, and that can integrate the many different services that are found in the server environment.

As mentioned above, many of VB's traditional strengths on the server, such as its database integration ability and its ability to encode business rules, are being eroded by XML technologies. If you have a database that can be queried and retrieved in a completely XML-based manner, then writing a VB-based component to encapsulate the database interactions can be handled just as readily through the effective use of XSLT. If you want to validate a set of invoices, you could just as readily use XML Schema.

However, the world does not yet operate in a completely XML-based environment, and won't for some time yet. This actually provides a usage for Visual Basic that is directly tied into XML (specifically, XSLT). One feature of the new MSXML3 parser is the ability to expose objects directly within XSLT, where the methods or properties can then be referenced through internal functions. The problem with this has to do with the fact that the referencing structure for methods and properties in the parser is flat--you could retrieve a property, but if that property in turn retrieved another object (unless the object was an XML object, but I'll get to that later), it would generate an error. Thus a function to directly access ADO recordsets is likely to fail because recordsets return fields, which are themselves objects.

You can, on the other hand, use Visual Basic to create a wrapper class for these services. By encapsulating and flattening hierarchies behind a set of VB interfaces, you could do such things as convert LDAP queries into XML documents, create and update databases, and perhaps most importantly, run other programs by passing data through XSLT filters: convert an incoming XML stream into an e-mail document to be sent, update an Excel spreadsheet with invoice information, even (though this is well beyond the scope of this article) create Visual Basic programs dynamically through the interfaces that control the Visual Basic IDE.

Consider a fairly simple example--creating a VB class that can load text files, save content out to text files, and save XML treeNodes to XML files. Within XSLT, the only objects that are explicitly recognized in XPath expressions are strings, numbers, or node-sets (under the guise of IXMLDOMNodeList or IXMLDOMSelection elements). Working with text files can be done with the File System Object (or FSO), which is a standard part of the IE Scripting package. Unfortunately, since you can't work with non-native XML items through this mechanism, you need to wrap these calls so that subroutines return empty strings and functions return suitably transformed output.

To create that library, select File->New from the Visual Basic IDE and choose ActiveX DLL, setting the name of the class so created to CFileAccess. You will also need to rename the project (here I named it CagleCom, but you may have different ideas!). You will then need to make a reference to the FSO object: select View-->References and choose "Microsoft Scripting Runtime". You'll also need to create a reference to the MSXML3 object.

Once you do that, you can then write the code to write three functions into the CFileAccess scripting window: ReadFile (which reads a text file and returns the result as a string), WriteFile (which takes the text contents of a node and its descendents and outputs them to a text file), loadXML (a poor man's document() function, which is not yet implemented in MSXML3) and saveXML (which saves the contents of an XML node). The full code for these functions is given in the following listing:


Option Explicit

Dim fso As New FileSystemObject



Public Function readFile(filePath As String) As String

    Dim ts As TextStream

    Dim msg As String

    On Error GoTo reportError

    Set ts = fso.OpenTextFile(filePath, ForReading, False)

    readFile = ts.ReadAll

    ts.Close

    Exit Function

reportError:

    msg = "Error Occurred:" + vbCrLf

    msg = msg + Err.Description

    readFile = msg

    On Error GoTo 0

End Function



Public Function saveXML(filePath As String,_

         xmlNode As Variant) As String

    Dim tempDoc As New DOMDocument

    Dim node As IXMLDOMNode

    Dim msg As String

    Dim txt As String

    On Error GoTo reportError

    If TypeName(xmlNode) = "DOMDocument" Then

        Set tempDoc = xmlNode

    Else

        Set node = xmlNode(0).cloneNode(True)

        tempDoc.loadXML node.xml

    End If

    tempDoc.save filePath

    Debug.Print filePath

    saveXML = ""

    Exit Function

reportError:

    msg = "Error Occurred:" + vbCrLf

    msg = msg + Err.Description

    saveXML = msg

    On Error GoTo 0

End Function



Public Function loadXML(filePath As String) _

         As IXMLDOMElement

    Dim msg As String

    Dim xmlDoc As New FreeThreadedDOMDocument

    On Error GoTo reportError

    xmlDoc.async = False

    xmlDoc.Load filePath

    Set loadXML = xmlDoc.selectSingleNode("*")

    Exit Function

reportError:

    msg = "Error Occurred:" + vbCrLf

    msg = msg + Err.Description

    xmlDoc.loadXML "<error>" + msg + "</error>"

    xmlDoc.selectNodes ("*")

    On Error GoTo 0

End Function



Public Function writeFile(filePath As String, _

        contents As Variant) As String

    Dim ts As TextStream

    Dim msg As String

    Dim output As String

    On Error GoTo reportError

    If TypeName(contents) <> "String" Then

        output = contents.Text

    Else

        output = contents

    End If

    Set ts = fso.OpenTextFile(filePath, ForWriting, True)

    ts.Write output

    ts.Close

    writeFile = ""

    Exit Function

reportError:

    msg = "Error Occurred:" + vbCrLf

    msg = msg + Err.Description

    writeFile = msg

    On Error GoTo 0

End Function

Once you've written the DLL, compile it (in my case, I compiled it as CagleCom.dll) and register it. Once you've done this, you can use it as an object in your XSLT files by passing it in through Microsoft XSLT Processor object. This particular interface, new with the most recent web-release, lets you create compiled style sheets that can be cached for significantly improved performance compared to the older transformNode method.

You can set up an ASP page to test this out. The following ASP script shows how to take an XML listing of absolute (local) text file references and populate an XML document with the content.


<%

set filterDoc=createObject("FreeThreadedDOMDocument")

set template=createObject("XSLTemplate")

set fileAccess=createObject("CagleCom.CFileAccess")

filterDoc.async=false

filterDoc.load "buildBook.xsl"

template.stylesheet=filterDoc

set processor=template.createStylesheet

set filesDoc=createObject("FreeThreadedDOMDocument")

filesDoc.async=true

filesDoc.load server.mapPath("chapterFiles.xml")

processor.input=filesDoc

processor.AddObject fileAccess, _

     "http://www.vbxml.com/schemas/fileAccess"

processor.output=response

processor.transform

%>

The file references themselves are kept in an XML document called chapterFiles.xml, which includes both the pointers for each file as well as the location of the base folder where all the files could be found. Note that files could be either of type text/plain or text/xml, which affects how they are output:


<!-- chapterFiles.xml -->

<files base_uri="d:\bin\">

 <file href="chapter1.txt" type="text/plain"/>

 <file href="chapter2.txt" type="text/plain"/>

 <file href="chapter3.xml" type="text/xml"/>

 <file href="chapter4.xml" type="text/xml"/>

</files>

The final XSLT code, in turn makes use of the object association (from the line processor.AddObject(fileAccess,"http://www.vbxml.com/schemas/fileAccess"), which associates a URI with an object, so that it can be referenced internally. The XSLT script in turn must similarly declare this namespace, and associate it with a prefix, as shown in buildBook.xsl:


<!-- buildBook.xsl -->

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:file="http://www.vbxml.com/schemas/fileAccess" 

version="1.0">

 <xsl:output method="xml" omit-xml-declaration="yes"/>

 <xsl:variable name="baseURI" select="//@base_uri"/>



 <xsl:template match="/">

  <xsl:apply-templates select="files"/>

  <xsl:variable name="xml_files">

   <files>

        <xsl:for-each select="//file[

            @type='text/xml']">

      <xsl:copy-of select="."/>

     </xsl:for-each>

   </files>

  </xsl:variable>

  <xsl:value-of select="file:saveXML(

         concat($baseURI,'testRun.xml'),$xml_files/*)"/>

 </xsl:template>

 

 <xsl:template match="files">

  <document>

   <xsl:apply-templates select="file"/>

  </document>

 </xsl:template>

 

 <xsl:template match="file">

  <xsl:choose>

     <xsl:when test="@type='text/xml'">

    <xsl:value-of select="file:readFile(

    concat($baseURI,string(@href)))" 

    disable-output-escaping="yes"/>

     </xsl:when>

     <xsl:when test="@type='text/plain'">

    <xsl:value-of select="file:readFile(

    concat($baseURI,string(@href)))" 

    disable-output-escaping="no"/>

     </xsl:when>

  </xsl:choose>

 </xsl:template>

Once a prefix is defined, the functions of the associated object can be called by prefacing the object's method name with the namespace prefix, in this case file:. In this way you can extend the functions available within XSLT. For example, the file:readFile() function lets you load in the contents of a text file and insert it into the document's output stream. Similarly, you could save the contents of a generated set of nodes (as shown for the variable $xml_files) using the file:saveXML function. Notice that file:readFile() also does double duty opening up both text and XML files--the primary difference between the two being that XML files disable output escaping while text files don't.

It should be noted here that I used a structure that is technically "illegal" in W3C XSLT--the creation of XML content within variable blocks. I did this for compatibility with the current MSXML parser, which does support this capability but does not yet support the node-set() functionality. When node-set() is implemented in that parser, you should use it to convert such XML blocks from text into fully qualified node sets.

The immediate application of this example code is fairly obvious--you can incorporate text files and save intermediate results back out to the server quickly and efficiently. The more subtle lesson is that you can use XSLT in conjunction with Visual Basic to communicate with almost any device that can communicate via XML--I've written applications that create voice responses transformed from XSLT (using the Kurzweil Speech synthesizer), connect to LDAP and Active Directory services, send mail messages, and so forth.

Will XSLT replace VB?

Not immediately. It is worth remembering that XSLT is in its first version, and there has not yet been a computer language that hasn't evolved and improved over time as people begin to push its capabilities and limitations. It is also a fairly ungainly language (although in some respects this points back to the precept that while XML is human readable, it is ultimately intended for machine consumption). Already the W3C is beginning to examine those limitations that XSLT has as a language.

This takes us back to the question. Visual Basic is fundamentally an integration language--designed in great part to integrate components together through either data exchange or programmatic interfaces. It is also a development environment, which cannot be said for XSLT. There is no prospect that the community of Visual Basic developers will all disappear overnight.

Having said that, VB will ultimately be replaced for many applications by the next couple of iterations of XSLT, just as Java will. These are, admittedly, fighting words to the many partisans of both languages, and I should qualify this statement somewhat to avoid getting too many flames. The XML/XSLT paradigm represents a concept that differs fundamentally from the procedural domain of both Java and VB--the code is data.

In a procedural realm, a data provider and a data consumer remain fixed to their host environments--a Java (or VB) RPC call sends data to another Java RPC component, which sends data back. We spend a great deal of time and energy transporting the blocks of our programming from machine to machine, and they follow the pattern of compile, transmit, (get a cup of coffee) receive, instantiate, run. To pull this off requires the agency of vast collections of unique pointers locked in registries and other resources, which are themselves easily corruptible, difficult to maintain, and space-consuming.

The XML/XSLT realm, on the other hand, sees data and transformations on that data as being continuous processes. In a metaphor that I badly botched on the MSDN web site, I likened the concept of XML/XSLT as having a certain symmetry with the interchange between electron-positron pairs and gamma rays, where it is essentially impossible to say which is energy and which is mass, which is data and which is program. When the components are themselves both data and code, then integration between these two visions becomes laughably simple.

As an example, let's take a look at SOAP. SOAP is essentially just a messaging format with a binding to a specifically identifiable HTTP header. In some respects, SOAP can be viewed in two lights. In one sense it is a bridge between procedural languages that solves a number of the complex handshaking and protocol mismatches that years of COM/CORBA infighting have left us. However, SOAP can also be seen as a first step movement away from procedural languages entirely--an XSLT implementation can both generate and consume SOAP messages very easily, and XSLT is ultimately independent of any specific platform or programming environment. Moreover, XSLT to SOAP to XSLT solutions can sit above the firewall, if we design well enough. When messaging moves to XML, integration isn't all that far behind.

We're obviously several years away from that concept, though. In a way, I suspect that most of the computer languages in use today will begin to take on more XML-like characteristics over time, so that it would be more accurate to say that VB and Java won't so much be pushed out by XML as be subsumed by it--semantic paradigms rather than structural/syntactic ones. Then the language wars will really get fun!