Doing XSLT with MSXML in C++

Environment: VC6 SP4+,C++ .NET 2002/2003

This article presents a short introduction to Microsoft's XSLT capabilities. It shows how to use XML transformations by using the MSXML SDK. There are of course tons of uses for this, but let me name at least one that made my life easier:

If you need to visualize your XML data in an HTML form in a WebBrowser control, for example, all you need to do is load your XML file (it can be stored in memory, or created on demand), create XSLT processor from a file stored in let's say application resources, and then invoke the transformation. The result is an HTML file that can be shown in a control. You don't need to hard code HTML tags anywhere in the C++ code. It's all in an XSLT file, prepared by your coworkers who don't need to be C++ programmers.

What You Should Already be Familiar With

Basic familiarity with XML and XSLT should be enough. I will give example files, but I recommend reading some other articles on these technologies to prevent any confusion. Knowledge about how to use COM will also be needed, although I will the show step-by-step procedure. Most of my problems with COM were connected with parameters—passing them or packing them into variant types or BSTR strings—so I will give you a few hints on how to deal with it. This tutorial can be thought of as an extension to Tom Archer's article from this site: "Introduction to Using the XML DOM from Visual C++," which I suppose you should read also.

MSXML SDK Basics

The Microsoft XML SDK is composed of several COM objects that are stored in a DLL library; the most recent one is msxml4.dll. You can access those objects through its interfaces by first creating instances and later querying it for the interface. In this tutorial, I will make use of the IXMLDOMDocument2, IXSLTemplate, and IXSLProcessor interfaces. No MFC support is required. I will only use basic wrapper classes, such as bstr_t and variant_t, that do not require any extra external linkage.

Following are the books.xml and trans.xsl files that will be used later in the sample project. They are very simple. Besides basic XSLT capabilities, the transformation file presents syntax that is needed to use parameters passed from the C++ application.

books.xml

<?xml version="1.0" encoding="iso-8859-1"?>
<document>
  <book>
    <author>Tiny Tim</author>
    <title>Grandma cakes</title>
    <price>30</price>
  </book>
  <book>
    <author>Jenna Summers</author>
    <title>Western dishes</title>
    <price>35</price>
  </book>
  <book>
    <author>Chris Fawlty</author>
    <title>British specials</title>
    <price>45</price>
  </book>
</document>

trans.xsl

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">
  <xsl:output method="html" encoding="windows-1250"/>
  <xsl:param name="maxprice" select="150"/>
  <xsl:template match="/">
    <html><body bgcolor="red">
    <xsl:apply-templates/>    
    </body></html>
  </xsl:template>
  <xsl:template match="book">
    <xsl:if test="price/text() &lt;= $maxprice">
      author : <xsl:value-of select="author"/><br/>
      title : <xsl:value-of select="title"/><br/>
      price : <xsl:value-of select="price"/><br/><br/>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Following is a result of the transformation; this is what we want to receive:

<html>
  <body bgcolor="red">
    author : Tiny Tim<br>
    title : Grandma cakes<br>
    price : 30<br><br>
    author : Jenna Summers<br>
    title : Western dishes<br>
    price : 35<br><br>
    author : Chris Fawlty<br>
    title : British specials<br>
    price : 45<br><br>
  </body>
</html>

Sample Project Using VC++ 6.0

I am not going to use MFC or ATL here so that I can make this code as basic as possible. Also, it might be useful if someone would like to integrate it with existing code that does not use those libaries. The source file is divided into the following parts:

  • COM initialization and loading of XML files
  • Initializing the XSLT processor
  • Doing the transformation
  • Printing the transformation to STDOUT

The changes will be done only to the main CPP file. I will present and explain the code line by line.

  1. Start your VC++ and create a new Win32 console application. Allow VC to generate a initial file with the "hello world" code.
  2. At first, we need to include a type library for MSXML. If you know that you have the fourth version, feel free to change it to msxml4.dll. The import directive includes two additional header files, <comip.h> and <comdef.h>, that include many functions and classes used later for COM manipulation.
  3. #include "stdafx.h"
    #import <msxml3.dll>  named_guids
    using namespace MSXML2;
    
  4. Now, it's time to declare all the interfaces that will be used later. There is a need for two IXMLDOMDocument interfaces because two XML files will be used. The IXSLTemplate will be created from the XSLT file and later, from this interface, the IXSLProcessor will be created. IStream will be used to store the result of the transformation.

    It's important to use the FreeThreadedDOMDocument object for the XMLDocument interface used to create the XSLTemplate. Each interface name ending with a Ptr sufix means that it's a smart pointer that encapsulates a given interface. Using them makes the code much shorter and easier to read.

  5. int main(int argc, char* argv[])
    {
      CoInitialize(NULL);
    
      variant_t vResult;
      void *output  = NULL;
      MSXML2::IXMLDOMDocumentPtr
              pXml(MSXML2::CLSID_DOMDocument);
      MSXML2::IXMLDOMDocumentPtr
              pXslt(CLSID_FreeThreadedDOMDocument);
      IXSLTemplatePtr pTemplate(CLSID_XSLTemplate);
      IXSLProcessorPtr pProcessor;
      IStream *pOutStream;
    
  6. Now both XML file with data and XSL file with transformation to HTML is loaded. _bstr_t is used to transform easily ANSI string to BSTR string. This class and also _variant_t used later are defined in comdef.h header file included by the #import directive.
  7.   try{
        // load xml file with data and xsl file to transform
        // xml -> html
        vResult = pXml->load(_bstr_t("books.xml"));
        vResult = pXslt->load(_bstr_t("trans.xsl"));
      } catch(_com_error &e) {
        printf("Error loading XML files : %s\n",
               (const char*)_bstr_t(e.Description()));
        exit(-1);
      }
    
  8. When we have properly initialized and loaded the XML files, it's time to initialize the template that will be used to create the processor. Before Microsoft added template functionality, each XSLT style sheet had to be compiled by the transformNode or transformNodeToObject methods each time a transformation was performed. Now, the XSLTemplate object can be used to cache the XSLT style sheet once and use it to transform several XML files with greater performance than before. An XSLProcessor object should be created for each transformation.
  9. try{
      vResult = pTemplate->putref_stylesheet(pXslt);
      pProcessor = pTemplate->createProcessor();
    } catch(_com_error &e) {
      printf("Error setting XSL style sheet : %s\n",
             (const char*)_bstr_t(e.Description()));
      exit(-1);
    }
    
  10. Now, it's time to configure our transformation. We need a buffer where all output will be put. This is done by creating a Stream object, and storing its interface pointer in a variable of the IStream type. This stream will be expanded automatically when the processor needs more space to store its output. It's important to remember that this is not an ANSI string stored in this stream, so it does not end with NULL. In later code, a processor is assigned its XML file that will be transformed; also, a variable is being assigned, with a value of 35.
  11. // prepare Stream object to store results of transformation,
    // and set processor output to it
      CreateStreamOnHGlobal(NULL,TRUE,&pOutStream);
      pProcessor->put_output(_variant_t(pOutStream));
    
      // attach to processor XML file we want to transform,
      // add one parameter, maxprice, with a value of 35, and
      // do the transformation
      vResult = pProcessor->put_input(_variant_t((IUnknown*)pXml));
      pProcessor->addParameter(_bstr_t("maxprice"),
                               _variant_t("35"),_bstr_t(""));
      pProcessor->transform();
    

If the transformation was succesful, the output can be read by the locking stream, and get a pointer to its begining. Before that, a NULL character is added to the end of the stream, so that later output to the console is easier. Even though all smart pointers can release themselves, this should happen before the uninitialization of COM.

  //get results of transformation and print them to stdout
  HGLOBAL hg = NULL;
  pOutStream->Write((void const*)"\0",1,0);
  GetHGlobalFromStream(pOutStream,&hg);
  output = GlobalLock(hg);
  printf("%s",(const char*)output);
  GlobalUnlock(hg);

  //release before CoUninitialize()
  pXml.Release();
  pXslt.Release();
  pTemplate.Release();
  pProcessor.Release();

  CoUninitialize();
  getchar();
  return 0;
}

Summary

I showed the basic usage of the XSLT processor in this article. Actually, using it is not very complicated. In my projects, I spent more time playing with the XSLT files than with the COM objects. This is what I wanted; all content processing is moved away from the application code. There are more very useful features of the XSLT processor. The main ones are setting variables that can be used by the XSLT code during processing, and specifying custom objects that the XSLT code can interact with. Such objects can be written by using ATL templates, but this is a topic for the next article.

Downloads

Download demo project - 6 Kb
Download source - 22 Kb


Comments

  • Good posting

    Posted by Legacy on 01/27/2004 12:00am

    Originally posted by: John

    Very nice and clear, thank you very much!

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

  • Hurricane Sandy was one of the most destructive natural disasters that the United States has ever experienced. Read this success story to learn how Datto protected its partners and their customers with proactive business continuity planning, heroic employee efforts, and the right mix of technology and support. With storm surges over 12 feet, winds that exceeded 90 mph, and a diameter spanning more than 900 miles, Sandy resulted in power outages to approximately 7.5 million people, and caused an estimated $50 …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds