This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.

MIND

MSXML 3.0 Supports XPath 1.0, XSLT 1.0, XDR, and SAX2

Aaron Skonnard

Code for this article: XMLFiles0900.exe (37KB)

If you've been watching the Microsoft® XML Web site (https://msdn.microsoft.com/xml), you've probably noticed the increased pace of MSXML releases. MSXML 2.0 shipped with Microsoft Internet Explorer 5.0 while MSXML 2.5 shipped with Windows® 2000. There was also an interim MSXML 2.6 release which included most of the features in the current 3.0 release with a few exceptions. There have also been several additional MSXML 3.0 releases this year. In this month's column, I'll describe the major enhancements made to MSXML since the original, version 2.0. These enhancements include new support for the official W3C XPath 1.0 and XSLT 1.0 specifications, improved support for the Microsoft XML Schema dialect known as XML-Data Reduced (XDR), and new support for the popular stream-based Simple API for XML (SAX). If you're not familiar with some of these terms, refer to The XML Files in the May 2000 issue, where I covered how all the core XML technologies fit together.

Core DOM API

      Since most of the development effort behind MSXML 3.0 has been focused on XPath, XSLT, XDR, and SAX functionality, very few visible enhancements have been made to the core DOM API since MSXML 2.0. MSXML 3.0 does, however, ship in a separate DLL (msxml3.dll) and uses different PROGIDs and coclass names to fully support side-by-side installation with MSXML 2.0. Figure 1 shows both the version-dependent and version-independent PROGIDs and coclass names that ship with MSXML 3.0. The MSXML team strongly recommends using the version-dependent forms in order to ensure that installing the next release will not affect existing services built and tested on MSXML 3.0.
      You can install MSXML 3.0 and take advantage of the new functionality while your existing code continues to use the MSXML 2.0 versions of the same components. The following code illustrates how to create an instance of the DOMDocument30 coclass (assuming a reference to the MSXML 3.0 type library):

  Dim doc as MSXML2.DOMDocument30
  
Set doc = New MSXML2.DOMDocument30

 

      Developers who like to live on the edge may want to install MSXML 3.0 as the default library by replacing the registry entries for the MSXML 2.0 version. When you install MSXML 3.0, a separate installation utility named xmlinst.exe is placed in the Windows system directory. Executing this file will replace the MSXML 2.0 registry entries with those for MSXML 3.0. Once this is done, all apps (including Internet Explorer 5.0) written against MSXML 2.0 will now run against MSXML 3.0 without any code changes.
      The nice thing about xmlinst.exe is that it actually lets you undo the registry changes. Figure 2 describes the different options that make it possible to easily switch between MSXML 2.0 and MSXML 3.0 registry settings.
      Besides these versioning issues, the most obvious addition to the core API can be found in the new IXMLDOMDocument2 interface (see Figure 3). All of the IXMLDOMDocument2 members are available through the dispatch interface of the DOMDocument and FreeThreadedDOMDocument coclasses so they can easily be used from scripting languages. All of these members will be discussed in more detail later.

XPath 1.0 APIs

      MSXML 2.0 provides support for XSL Patterns, the precursor to XPath 1.0. The notion of an XML addressing language was introduced into the original W3C XSL Working Drafts (https://www.w3.org/TR/1998/WD-xsl-19981216.html) and called XSL Patterns. MSXML 2.0 implements the XSL Patterns language as described in the original XSL specification with a few minor exceptions.
      MSXML 3.0 provides support for the legacy XSL Patterns syntax as well as XPath 1.0. You can take advantage of both through the selectNodes and selectSingleNode methods available on the IXMLDOMNode interface. The selectNodes method takes a string expression (either XPath 1.0 or XSL Pattern) and returns a reference to an IXMLDOMNodeList interface representing the collection of nodes matching the expression. The following code snippet illustrates the use of selectNodes in Visual Basic®:

  Public sub applyExpression(node as IXMLDOMNode, e as String)
  
dim nl as IXMLDOMNodeList, n as IXMLDOMNode
set nl = node.selectNodes(e)
for each n in nl
processNode n
next
end sub

 

The selectSingleNode method simply returns a reference to the first node that matches the expression

  dim n as IXMLDOMNode
  
set n = node.selectSingleNode("/foo/bar/baz")

 

which is equivalent to:

  dim n as IXMLDOMNode
  
set n = node.selectNodes("/foo/bar/baz").item(0)

 

      Since the expression can use either the XPath 1.0 or XSL Patterns syntax, there needs to be a way to specify the selection language before using these APIs. To maintain backwards compatibility with existing code, the default selection language is XSL Patterns. To change the current selection language to XPath, call the new setProperty method (see IXMLDOMDocument2) using the SelectionLanguage property name and the value XPath:

  node.ownerDocument.setProperty "SelectionLanguage", "XPath"
  
applyExpression node, "/child::foo/descendant::bar/baz"

 

To set the selection language back to XSL Patterns, simply call setProperty again using the value XSLPattern.
      A new implementation of selectNodes was also introduced in MSXML 3.0. This new version returns an object that implements both the IXMLDOMNodeList interface as well as the new IXMLDOMSelection interface. Again, to maintain backwards compatibility, the new version of selectNodes still returns a reference to an IXMLDOMNodeList interface. So to obtain a reference to the IXMLDOMSelection interface, a standard QueryInterface is required on the returned object. In Visual Basic, this can be accomplished through a Set operation:

  Dim sel as IXMLDOMSelection
  
Set sel = node.selectNodes("/child::foo/descendant::bar")

 

      The IXMLDOMSelection interface extends IXMLDOMNodeList and makes it possible to reuse a cached compiled expression (see Figure 4). The expr property makes it possible to control the value of the expression and the context property allows you to control the context node used when evaluating the expression. The following Visual Basic code snippet illustrates how this works:

  xml="<foo><bar><baz>hello</baz><baz>world</baz></bar></foo>"
  
doc.loadXML xml
doc.setProperty "SelectionLanguage", "XPath"
dim sel as IXMLDOMSelection
set sel = doc.documentElement.selectNodes("child::bar/*")
MsgBox sel.expr ' displays "child::bar/*"
MsgBox sel.length ' displays "2"
set sel.context = doc
MsgBox sel.length ' displays "0"
set sel.context = doc.documentElement
MsgBox sel.length ' displays "2"

 

      The specified context node can even come from a different document as long as it shares the same threading model as the document that created the selection object, as shown here:

  Xml1="<foo><bar><baz>hello</baz></bar></foo>"
  
Xml2="<foo><bar><baz>bye</baz></bar></foo>"
doc1.loadXML xml1
doc2.loadXML xml2
doc1.setProperty "SelectionLanguage", "XPath"
doc2.setProperty "SelectionLanguage", "XPath"
dim sel as IXMLDOMSelection
set sel = doc1.documentElement.selectNodes("child::bar/*")
MsgBox sel.item(0).xml ' displays "<baz>hello</baz>"
set sel.context = doc2.documentElement
MsgBox sel.item(0).xml ' displays "<baz>bye</baz>"

 

      There is also a matches method that checks to see if the node passed in is contained in the current selection as shown here:

  set n = doc.documentElement.childNodes(0).childNodes(0)
  
set res = sel.matches(n)
' if n is in the selected collection, res is not nothing

 

      The IXMLDOMNodeList methods are still used to traverse the collection (item and nextNode) while IXMLDOMSelection also makes it possible to peek at the current node (peekNode), remove nodes from the collection (removeAll and removeNext), and clone the entire collection (clone).
      One other XPath enhancement that you can expect sometime soon has to do with proper namespace support. According to the XPath specification, when expressions contain unqualified element names (no namespace prefix), the elements are considered part of no namespace (even if a default namespace declaration is present in the source document). When XPath expressions contain qualified names (through a namespace prefix) they are evaluated according to the namespace bindings that make up the current XPath context. However, there is currently no mechanism in place for establishing namespace bindings prior to evaluating an XPath expression in MSXML 3.0. This makes it impossible to properly query namespace-aware XML documents.
      For example, assuming the following XML document:

  <foo xmlns='urn:foo-bar:baz'>
  
<bar><baz/></bar>
</foo>

 

what should be returned by the /foo/bar/baz expression? According to the XPath specification, it should return nothing because it's looking for foo, bar, and baz elements that belong to no namespace. But in the source document the foo, bar, and baz elements all belong to the urn:foo-bar:baz namespace.
      To solve this problem, in a future release of MSXML 3.0 it will be possible to specify namespace bindings through the SelectionNamespaces property before calling selectNodes, as shown here:

  doc.setProperty "SelectionNamespaces", _
  
"xmlns:f='urn:foo-bar:baz'"
set sel = doc.selectNodes("/f:foo/f:bar/f:baz")

 

With the namespace bindings in place, this expression should now behave properly because it identifies foo, bar, and baz elements that belong to the 'urn:foo-bar:baz', which matches the default namespace declaration in the source document. (Note that syntax may change by release time.)

XSL Patterns versus XPath 1.0

      XSL Pattern expressions look very similar to the abbreviated form of XPath expressions. The main differences between the two languages are that XSL Patterns don't support the notion of distinct axes, and the available operators and function libraries are substantially different. Both languages follow the same processing model (for the most part), but XPath 1.0 is more powerful and flexible.
      To assist in learning the differences between the two syntaxes, I've provided a sample DHTML application that allows you to load an XML document in a frame and perform either XPath or XSL Pattern expressions against it (see Figure 5). You can see the applications in action at https://staff.develop.com/aarons/bits/xpath-builder.

Figure 5 Interactive Expression Builder

Figure 5 Interactive Expression Builder

      In response to popular demand, I've also provided a matrix (see Figure 6) that shows how the language constructs from XSL Patterns map to the language constructs of XPath 1.0 (where applicable). The latest release of MSXML 3.0 implements all the features listed in Figure 6, all of the XPath axes (except for namespace), and the entire XPath 1.0 function library as documented in the official W3C specification .
      For more background information on XPath 1.0, point your Web browser to https://www.w3.org/TR/xpath or check The XML Files in the July 2000 issue of MSDN® Magazine.

XSLT 1.0

      MSXML 2.0 provided two methods on the IXMLDOMNode interface for performing XSL transformations: transformNode and transformNodeToObject. The former returned the result of the transformation as a string while the latter returned the result of the transformation as a DOMDocument. These two methods still exist in MSXML 3.0 and can also be used to perform XSLT 1.0 transformations. Thanks to these methods, performing a transformation is as easy as loading a couple of documents and calling a single method:

  Dim xmlDoc as New DOMDocument, xslDoc as New DOMDocument
  
Dim strResult as String, xmlOutput as New DOMDocument
xmlDoc.load "foo.xml"
xslDoc.load "foo.xsl"
' return result as string
strResult = xmlDoc.transformNode(xslDoc.documentElement)
' return result as DOMDocument
xmlDoc.transformNode xslDoc.documentElement, xmlOutput

 

      The drawback of using these methods is decreased performance. Every time your code calls transformNode, the supplied stylesheet has to be compiled by the transformation engine. In MSXML 3.0, you can now cache compiled stylesheets for future use through the XSLTemplate30, which implements the IXSLTemplate interface (see Figure 7). The corresponding API is a little more cumbersome, but the performance benefits are well worth the effort.
      The following function illustrates how to load an XSLT stylesheet and cache it in the XSLTemplate30 object for future use:

  Function createCachedStylesheet(xsluri As String) As IXSLTemplate
  
' create free-threaded document and load stylesheet
Dim xslDoc As New FreeThreadedDOMDocument30
xslDoc.Load xsluri
' create template object and cache stylesheet
Dim temp As New XSLTemplate30
Set temp.stylesheet = xslDoc
Set createCachedStylesheet = temp
End Function

 

      You must use the freethreaded version of the document object (FreeThreadedDOMDocument30) with this new API. Once you have a cached stylesheet, you can create processor objects through the createProcessor method. A processor object implements the IXSLProcessor interface (see Figure 8) and represents an executing transformation. Multiple processor objects can be created for a given stylesheet.
      You can associate the source XML document (the document to be transformed) with the processor through its input property. Then, to perform the transformation, simply call transform. The code in Figure 9 contains a complete example.
      If you're using parameters in your XSLT transformations, you can add those parameters to the processor's context before calling transform. The IXSLProcessor interface has an addParameter method for this purpose:

  Dim processor As IXSLProcessor
  
Set processor = cache.createProcessor()
processor.addParameter "param1", "hello"
processor.addParameter "param2", "world"
processor.input = doc1
processor.Transform

 

This code assumes that the stylesheet declares the parameters param1 and param2 as follows:

  <xsl:transform version="1.0"
  
xmlns:xsl="https://www.w3.org/1999/XSL/Transform">
<xsl:param name="param1"/>
<xsl:param name="param2"/>
...
</xsl:transform>

 

      MSXML 3.0 also makes it possible to extend the capabilities of the XSLT language through custom script libraries and COM components. To add a script block to your XSLT transformation, use the msxml:script element as illustrated in Figure 10.
      XSLT transformations can even leverage custom COM components that are added to the processor's context via the addObject method.

   Set obj = CreateObject("mylib.myobject")
  
processor.addObject obj, "urn:mylib"
processor.Transform

 

      Assuming the mylib.myobject component supports a GetMessage function and a time property, you can make use of them in a transformation using the appropriate namespace qualifier.

  <xsl:transform version="1.0"
  
xmlns:xsl="https://www.w3.org/1999/XSL/Transform"
xmlns:mylib="urn:mylib">
<xsl:template match="/">
Message: <xsl:value-of select="mylib:GetMessage()"/>
Time: <xsl:value-of select="mylib:get-time()"/>
</xsl:template>
</xsl:transform>

 

Notice that property names must be prefixed with "get-" and accessed like methods (notice the parentheses).

XSL versus XSLT 1.0

      The XSL implementation in MSXML 2.0 was based on one of the original Working Draft specifications. Therefore it doesn't support many of the language constructs that exist in the final XSLT 1.0 specification. MSXML 3.0 supports virtually the entire XSLT 1.0 specification. At the time of publication, the unimplemented features consisted of the following: xsl:strip-space, xsl:preserve-space, xsl:apply-imports, xsl:namespace-alias, xsl:number, xsl:message, and xsl:fallback, case-order and lang attributes, and named attribute sets. These constructs are planned to be implemented in a future release.
      The MSXML team has provided a meta-stylesheet for transforming legacy XSL files into XSLT 1.0-compatible files. You can find this utility at https://msdn.microsoft.com/downloads/default.asp?URL=/code/topic.asp?URL=/msdn-files/028/000/072/topic.xml. A quick study of this stylesheet and the accompanying notes will help you understand the differences between the two languages. I've also provided a sample DHTML app that lets you load an XML document into a frame, type in an XSL(T) transformation, add parameters to the processor's context, execute the transformation, and display the results (see Figure 11). You can run the application online at https://staff.develop.com/aarons/bits/dhtml-xslt.

Figure 11 Pattern Matching Tool

Figure 11 Pattern Matching Tool

      For more background information on XSLT 1.0, point your Web browser to https://www.w3.org/TR/xslt. Also, see "XSLT Alleviates XML Schema Incompatibility Headaches," by Don Box, John Lam, and myself in the August 2000 issue of MSDN Magazine.

XDR

      MSXML 2.0 supports a schema language known as XML-Data Reduced (XDR), which is based on a proposal that Microsoft submitted to the W3C in 1998 (see https://www.w3.org/TR/1998/NOTE-XML-data-0105). As far as the API is concerned, validation against an XDR schema works the same as with a DTD. As long as the instance document uses the appropriate x-schema namespace to associate itself with its schema, and the document's validateOnParse property is set to true, MSXML will perform validation against the associated XDR schema while loading the document. Assuming the following XML document:

  <foo xmlns="x-schema:fooschema.xdr">
  
���
</foo>

 

the following code fragment illustrates how to load the document with validation:

  Dim doc as new DOMDocument
  
doc.validateOnParse = true
doc.load "foo.xml"

 

      Once loaded into the DOM, the document's schema information is available to the application through several Microsoft DOM extensions including datatype, nodeTypedValue, and definition.

  <foo xmlns:dt='urn:schemas-microsoft-com:datatypes' >
  
<bar dt:dt='i4' >842</bar>
<bar dt:dt='r8' >92.1</bar>
</foo>

 

      The following code illustrates how you can use both datatype and nodeTypedValue to leverage type information:

  Dim n1 As IXMLDOMNode
  
Dim n2 As IXMLDOMNode
Set n1 = doc.selectSingleNode("/foo/bar[0]")
Set n2 = doc.selectSingleNode("/foo/bar[1]")
Dim v1, v2 As Variant
v1 = n1.nodeTypedValue
v2 = n2.nodeTypedValue
MsgBox (TypeName(v1) = "Long") ' displays True
MsgBox (TypeName(v2) = "Double") ' displays True
MsgBox (n1.dataType = "i4") ' displays True
MsgBox (n2.dataType = "r8") ' displays True

 

Assuming the loaded document has a complete XDR schema associated with it (containing ElementType and AttributeType declarations), you can also use the definition property to pull out the actual schema definition for a particular node:

  Dim n as IXMLDOMNode
  
Set n = doc.documentElement.childNodes(2)
MsgBox n.definition
' displays <ElementType name='bar' dt:type='i4'/>

 

      As with the XPath and XSLT enhancements, the enhancements to the XDR-related APIs were driven by the desire for improved efficiency and better performance. In MSXML 3.0, it's possible to cache parsed XDR schema definitions and reuse them with between instance documents. To accomplish this, you must create an instance of an XMLSchemaCache coclass. This class implements the new IXMLDOMSchemaCollection interface (see Figure 12) and is used as follows:

  Dim cache as IXMLDOMSchemaCollection
  
Set cache = New XMLSchemaCache30
Dim xdrdom as New DOMDocument30
xdrdom.async = false
xdrdom.load "bar.xdr"
' add an a parsed XDR to the cache
cache.add "urn:bar", xdrdom
' parse/load the specified XDR and add to cache
cache.add "urn:baz", "https://develop.com/baz.xdr"
' add an entire namespace collection
cache.addCollection doc.namespaces ' from previous example

 

      Notice that you can add schemas to the cache through either the add or addCollection methods. The add method takes either a URI to an XDR file or an IXMLDOMDocument reference to a loaded XDR file. The addCollection method takes an IXMLDOMSchemaCollection reference. The namespaces property of the IXMLDOMDocument2 interface conveniently returns a reference to a schema collection representing the schemas associated with the loaded document.
      Once the cache is populated, any document can latch onto it through the IXMLDOMDocument2 schemas property:

  ' assuming doc1, doc2, and doc3 are dim'ed as DOMDocument30
  
' async flag has already been set to false and
' validateOnParse flag has already been set to true
doc1.schemas = cache
doc2.schemas = cache
doc3.schemas = cache
doc1.load "foo.xml"
doc2.load "bar.xml"
doc3.load "baz.xml"

 

When each of these documents loads, the document will be validated against the associated schema that already exists in the cache.
      Before MSXML 3.0, validation was only possible during document load. A new method, validate, was introduced via the IXMLDOMDocument2 interface to allow revalidation of loaded documents. Using validate, documents can be revalidated after mutation events as shown here:

  On Error Goto errHandler
  
doc1.schemas = cache
doc1.load "foo.xml"
MutateTheHeckOutOfDocument doc1
doc1.validate
' check Err for COM exception

 

      There will probably be several other enhancements to the schema-related APIs when MSXML introduces support for the official W3C XML Schema Definition Language (XSD).

Updates to XDR Syntax

      In terms of XDR syntax, there were only two major enhancements: support for inline schemas and support for MinLength/MaxLength attributes. Inline schemas make it possible to embed a schema definition within the schema instance document; previously they had to exist in separate files. This is very similar to how DTDs work with respect to the internal DTD subset. Inline schemas follow the same syntax as a regular schema definition except they are identified by the name attribute and linked to the instance document through an x-schema namespace declaration that refers to the schema by name.

  <foo xmlns:data="x-schema:#myInlineSchema">
  
<Schema name="myInlineSchema"
xmlns="urn:schemas-microsoft-com:xml-data"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<ElementType name="bar" dt:type="int"/>
</Schema>
<data:bar>28</data:bar>
</foo>

 

      Two new attributes, minLength and maxLength, were also added to the urn:schemas-microsoft-com:datatypes namespace for constraining the length of the string, number, bin.hex, and bin.base64 datatypes. Besides these minor additions to the XDR language, the XDR implementation in MSXML 3.0 is equivalent to MSXML 2.0. For more background information on XDR, see the online MSDN documentation at https://msdn.microsoft.com/xml.

New SAX2 Support

      One of the most recent additions to MSXML 3.0 is a C++/COM implementation of SAX2. Although the original release was targeted towards C++/COM, MSXML is slated to also provide a Visual Basic mapping layer that should be available by the time this article hits the newsstands.
      The SAX2 API offers a more performance-sensitive processing model than the DOM because it doesn't rely on building an in-memory tree representation of the underlying XML document. SAX2 is a stream-based API built on the merits of interface-based programming. A SAX2 consumer application implements one or more of the SAX2 consumer interfaces and registers them with a SAX2 producer application. The producer can send an XML document to the consumer through the methods available on the SAX2 consumer interfaces.
      For example, the following code snippet sends a simple XML document to the supplied consumer:

  void function sendMeDocument(ISAXContentHandler* pConsumer) {
  
pConsumer->startDocument();
// parameters omitted for clarity...
pConsumer->startElement(..., L"foo", ...);
pConsumer->characters(L"hello world", ...);
pConsumer->endElement(..., L"foo", ...);
pConsumer->endDocument();
}

 

      The XML document sent to the consumer can be serialized as follows:

  <foo>hello world</foo>
  

 

      A very common type of SAX2 producer is an XML parserâ€"as the parser processes the physical XML documents, it makes the corresponding method calls into a SAX consumer.
      The MSXML 3.0 support for SAX2 can be found in the files that are installed into the MSXML 3.0 SDK inc directory. The parser is contained in the SAXXMLReader coclass. The main interfaces that you should become familiar with are ISAXXMLReader and ISAXContentHandler. In the next release of MSXML, the SAX interfaces are slated to be integrated with the DOM interfaces, which would mean there would no longer be separate xmlsax.h and xmlsax_l.c files.
      SAX2 is a broad enough topic for a future column. To hold you over in the meantime, check out the SAX2 articles on MSDN. The SAX2 Jumpstart article (https://msdn.microsoft.com/workshop/xml/articles/sax2jumpstart.asp) walks you through the basics of building your first SAX2 application and the Joy of SAX article (https://msdn.microsoft.com/workshop/xml/articles/joyofsax.asp) demonstrates how to build a temporary Visual Basic mapping layer. For more background information on SAX2, point your browser to David Megginson's site at https://www.megginson.com.

Utilities

      Besides the XSL to XSLT 1.0 converter utility mentioned earlier, there are several other utilities that the MSXML team has made available along with MSXML 3.0. They've provided a couple of shell extensions for validating XML and viewing the results of XSLT programs from inside of Internet Explorer 5.0 (see Figure 13).

Figure 13 Validating XML

Figure 13 Validating XML

They've also included an ISAPI filter that enables server-side XSL formatting of XML content for various browsers and devices. And finally, they've provided an XSLT transformation for XDR that automatically generates documentation for a given schema (see Figure 14). You can download all of these utilities from https://msdn.microsoft.com/downloads/default.asp?URL=/code/topic.asp?URL=/msdn-files/028/000/072/topic.xml.

Figure 14 Schema Documentation Generator

Figure 14 Schema Documentation Generator

Where are We?

      MSXML 3.0 has come a long way since MSXML 2.0. It now supports the W3C-sanctioned XPath 1.0 and XSLT 1.0 specifications and has improved support for XDR. Several performance-related enhancements were achieved by making it possible to cache compiled XPath expressions, compiled XSLT stylesheets, and loaded XDR schemas. This new functionality is made possible through the new IXMLDOMDocument2, IXMLDOMSelection, IXSLTemplate, IXSLProcessor, and IXMLDOMSchemaCollection interfaces. And as if that wasn't enough, MSXML 3.0 recently added support for SAX2.

Aaron Skonnard is an instructor and researcher at DevelopMentor, where he develops the XML curriculum. Aaron coauthored* Essential XML (Addison-Wesley Longman, 2000) and wrote Essential WinInet *(Addison-Wesley Longman, 1998). Get in touch with Aaron at https://staff.develop.com/aarons.

From the September 2000 issue of MSDN Magazine.