XSL Translation: Powerful, Misunderstood, and Mislabeled

Back in the year 1999, XSL and CSS (both dubbed “stylesheet languages”) were unofficial competitors. CSS ended up becoming the “winner”. This was actually a good thing for XSL, which was considered to be more powerful and complex than CSS.
CSS is all well and good for the applying of styles to online content (such as Web pages), but the true power of XSL lies in its ability to translate XML into any other text-based format. XSL can be used completely outside of the context of browsers and Web pages; all you need is a good XSLT processor that you can use in your code (sample code provided below).

An important thing to note, at this point, is that there will be no discussion of any “browser” or “HTML” in this post, nor will there be any discussion of behavior that one would describe as “stylesheet-like”. Instead, the true power of XSL translation will be illustrated.

Example: Converting XML Into CSV Text

This example will illustrate conversion of XML data into CSV (Comma Separated Value, an input format that is supported by Excel, for example).

Here is the input XML:

<?xml version="1.0" encoding="utf-8"?>
<DataTable name="The Data">
 <Row name="Header Row">
  <Column name="Column 1" />
  <Column name="Column 2" />
  <Column name="Column 3" />
 </Row>
 <Row name="Row 1">
  <Column value="Alpha"/>
  <Column value="Bravo"/>
  <Column value="Charlie"/>
 </Row>
 <Row name="Row 2">
  <Column value="Delta"/>
  <Column value="Echo"/>
  <Column value="Foxtrot"/>
 </Row>
 <Row name="Row 3">
  <Column value="Golf"/>
  <Column value="Hotel"/>
  <Column value="India"/>
 </Row>
</DataTable>

Here is the desired spreadsheet layout:

Column 1 Column 2 Column 3
Alpha Bravo Charlie
Delta Echo Foxtrot
Golf Hotel India

 

Here is the XSL translation code (more information on XSLT available here):

<?xml version='1.0'?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

    <xsl:variable
    name="dataTableName"
    select="/DataTable/@name"/>

    <xsl:template match="/DataTable">
        <xsl:choose>
            <xsl:when test="$dataTableName">
                Data Table Name: "<xsl:value-of select="$dataTableName"/>"&#13;&#10;
            </xsl:when>
            <xsl:otherwise>Data Table Name: (no name)&#13;&#10;</xsl:otherwise>
        </xsl:choose>
        <xsl:apply-templates select="Row"/>
    </xsl:template>

    <xsl:template match="Row">
        <xsl:if test="@name">
            <xsl:value-of select="@name"/>,
        </xsl:if>
        <xsl:apply-templates select="Column"/>
        <!--  add a line break (0D0A) between rows -->
        <xsl:if test="following-sibling::Row">&#13;&#10;</xsl:if>
    </xsl:template>

    <xsl:template match="Column">
        <xsl:choose>
            <xsl:when test="@name">
                <xsl:value-of select="@name"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:if test="@value">
                    <xsl:value-of select="@value"/>
                </xsl:if>
            </xsl:otherwise>
        </xsl:choose>
        <!--  add a comma between columns -->
        <xsl:if test="following-sibling::Column">,</xsl:if>
    </xsl:template>

</xsl:stylesheet>

Here is C++ source code that will perform the translation (using MSXML):

#define WIN32_LEAN_AND_MEAN

#include <stdio.h>
#import <msxml3.dll> named_guids
using namespace MSXML2;

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;

   try
   {
      vResult = pXml->load(_bstr_t("input.xml"));
      vResult = pXslt->load(_bstr_t("translate.xslt"));
   }
   catch(_com_error &e)
   {
      printf(
         "Error loading XML/XSL: %s\n",
         (const char*)_bstr_t(e.Description()));
      exit(-1);
   }

   try
   {
      vResult = pTemplate->putref_stylesheet(pXslt);
      pProcessor = pTemplate->createProcessor();
   }
   catch(_com_error &e)
   {
      printf(
         "Error initializing XSL translator: %s\n",
         (const char*)_bstr_t(e.Description()));
      exit(-1);
   }

   CreateStreamOnHGlobal(NULL,TRUE,&pOutStream);
   pProcessor->put_output(_variant_t(pOutStream));

   vResult = pProcessor->put_input(_variant_t((IUnknown*)pXml));
   pProcessor->transform();

   HGLOBAL hg = NULL;
   pOutStream->Write((void const*)"\0",1,0);
   GetHGlobalFromStream(pOutStream,&hg);
   output = GlobalLock(hg);
   // advance past (and ignore) BOM:
   printf("%S",(((const char*)(output)) + 2));
   GlobalUnlock(hg);

   pXml.Release();
   pXslt.Release();
   pTemplate.Release();
   pProcessor.Release();

   CoUninitialize();

   return 0;
(original source available here)

Here is the command-line to use (with the above program):

convert > output.csv

Here is an example of the output:

Data Table Name: "The Data"
Header Row,Column 1,Column 2,Column 3
Row 1,Alpha,Bravo,Charlie
Row 2,Delta,Echo,Foxtrot
Row 3,Golf,Hotel,India

Happy translating!