| Recipe 12.4. Generating Data WrappersProblemYou want to create classes 
 
 that wrap the data
contained in each message with a type-safe interface. SolutionThe solution works in two modes. If a message name is provided in a
parameter, then it generates a wrapper only for that message data.
Otherwise, if no message is specified, it generates wrappers for all
messages: <xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
  <xsl:output method="text"/>
  <xsl:strip-space elements="*"/>
   
  <!--The message to generate data for. '*' for all -->
  <xsl:param name="message" select=" '*' "/>
  <!--The directory to generate code -->
  <xsl:param name="generationDir" select=" 'src/' "/>
  <!--The C++ header extension to use -->
  <xsl:param name="headerExt" select=" '.h' "/>
  <!--The C++ source extension to use -->
  <xsl:param name="sourceExt" select=" '.C' "/>
   
  <!--Key to locate data types by name -->
  <xsl:key name="dataTypes" match="Structure" use="Name" /> 
  <xsl:key name="dataTypes" match="Primitive" use="Name" /> 
  <xsl:key name="dataTypes" match="Array" use="Name" /> 
  <xsl:key name="dataTypes" match="Enumeration" use="Name" /> 
  
  <!-- Top-level template determines which messages to process -->
  <xsl:template match="/">
    <xsl:choose>
        <xsl:when test="$message = '*'">
          <xsl:apply-templates select="*/Messages/*"/>
        </xsl:when>
        <xsl:when test="*/Messages/Message[Name=$message]">
          <xsl:apply-templates select="*/Messages/Message[Name=$message]"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:message terminate="yes">No such message name 
          [<xsl:value-of select="$message"/>]</xsl:message>
        </xsl:otherwise>
      </xsl:choose>
  </xsl:template>
   
<!-- If the messages data type is contained in the repository, then generate data 
wrapper header and source file for it -->
<xsl:template match="Message">
  <xsl:choose>
    <xsl:when test="key('dataTypes',DataTypeName)">
      <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="header"/>
      <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="source"/>
    </xsl:when>
    <xsl:otherwise>
            <xsl:message>Message name [<xsl:value-of select="Name"/>] uses data
            [<xsl:value-of select="DataTypeName"/>] that is not defined in the 
            repository.</xsl:message>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>     
   
<!-- We only generate headers if a messages data type is a Structure. 
The only other typical message data type is XML. We don't generate wrappers for XML 
payloads.-->
<xsl:template match="Structure" mode="header">
<xsl:document href="{concat($generationDir,Name,$headerExt)}">
#include <primitives/primitives.h>
   
class <xsl:value-of select="Name"/>
{
public:<xsl:text>

</xsl:text>
  <xsl:for-each select="Members/Member">
    <xsl:text>    </xsl:text>
    <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="returnType"/>
    get_<xsl:value-of select="Name"/>( ) const ;<xsl:text/>
    <xsl:text>
</xsl:text>
   </xsl:for-each>
<xsl:text>
</xsl:text>
private:<xsl:text>

</xsl:text>
  <xsl:for-each select="Members/Member">
    <xsl:text>    </xsl:text>
    <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="data"/>  m_
<xsl:value-of select="Name"/> ;<xsl:text/>
    <xsl:text>
</xsl:text>
  </xsl:for-each>
} ;
</xsl:document>
</xsl:template>
   
<!-- We only generate source if a messages data type is a Structure. -->
<!-- The only other typical message data type is XML. We don't          -->
<!-- generate wrappers for XML payloads.                                          -->
<xsl:template match="Structure" mode="source">
<xsl:document href="{concat($generationDir,Name,$sourceExt)}">
#include "<xsl:value-of select="Name"/><xsl:value-of select="$headerExt"/>"
   
<xsl:text/>
   
  <xsl:for-each select="Members/Member">
    <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="returnType"/>
    <xsl:text>  </xsl:text>
    <xsl:value-of select="../../Name"/>::get_<xsl:value-of select="Name"/>( ) const
    <xsl:text>
</xsl:text>
    <xsl:text>{
</xsl:text>
    <xsl:text>    return m_</xsl:text><xsl:value-of select="Name"/>
    <xsl:text>;
</xsl:text>
    <xsl:text>}

</xsl:text>
   </xsl:for-each>
   
</xsl:document>
</xsl:template>
   
<!-- We assume members that are themselves structures are -->
<!-- returned by reference. -->
<xsl:template match="Structure" mode="returnType">
const <xsl:value-of select="Name"/>&<xsl:text/>
</xsl:template>
   
<!-- We map primitives that can be represented by native C++ types to those native 
types. -->
<!-- Otherwise we assume the primitive is externally defined. -->
<xsl:template match="Primitive" mode="returnType">
  <xsl:choose>
    <xsl:when test="Name='Integer' ">int</xsl:when>
    <xsl:when test="Name='Real' ">double</xsl:when>
    <xsl:otherwise><xsl:value-of select="Name"/></xsl:otherwise>
  </xsl:choose>
</xsl:template>
   
<xsl:template match="*" mode="returnType">
<xsl:value-of select="Name"/>
</xsl:template>
   
<xsl:template match="Primitive" mode="data">
  <xsl:choose>
    <xsl:when test="Name='Integer' ">int</xsl:when>
    <xsl:when test="Name='Real' ">double</xsl:when>
    <xsl:otherwise><xsl:value-of select="Name"/></xsl:otherwise>
  </xsl:choose>
</xsl:template>
   
<xsl:template match="*" mode="data">
<xsl:value-of select="Name"/>
</xsl:template>
   
</xsl:stylesheet>
 This generator produces only a
get
interface, but you can easily extend it to generate set functions or
other types of functions. Here is a sample generated header file: #include <primitives/primitives.h>
   
class AddStockOrderData
{
public:
   
    StkSymbol get_symbol( ) const ;
    Shares get_quantity( ) const ;
    BuyOrSell get_side( ) const ;
    OrderType get_type( ) const ;
    double get_price( ) const ;
   
   
private:
   
    StkSymbol  m_symbol ;
    Shares  m_quantity ;
    BuyOrSell  m_side ;
    OrderType  m_type ;
    double  m_price ;
   
} ;
 Here is a sample
cpp file: #include "AddStockOrderData.h"
   
StkSymbol  AddStockOrderData::get_symbol( ) const
{
    return m_symbol;
}
   
Shares  AddStockOrderData::get_quantity( ) const
{
    return m_quantity;
}
   
BuyOrSell  AddStockOrderData::get_side( ) const
{
    return m_side;
}
   
OrderType  AddStockOrderData::get_type( ) const
{
    return m_type;
}
   
double  AddStockOrderData::get_price( ) const
{
    return m_price;
}
 DiscussionThis section uses the term wrapper to denote a
class that provides an object-oriented interface to data that is
otherwise just a plain old C struct. I once worked on a project that
hand-coded all our message wrappers. Although the work was tedious,
the result was well worth the effort. Consider a message that
contains prices, quantities, and dates. An integer type might encode
both of these higher-level types. You could easily make a mistake and
substitute one for the other without the compiler noticing. Wrappers
provide a way to put a skin around your message data that converts
low-level representations to class-based primitives such as
Price, Qty, and
Date. An autogenerated wrapper provides this
benefit with less effort. A message repository and XSLT-based generator allow you to automate
the task of producing wrappers. In practice, wrappers sometimes
contain some smarts, and you might need to store additional metadata
in the repository to get corresponding code-generation smarts. One
common case occurs when message data contains arrays. Often another
field is present that states how many items are actually stored in
the array. If you hand-coded a wrapper function to add an item to
this array, it would need to reference this field to find the next
empty locations and increment it after adding the new data. You could
generate such     code only if the repository associated
the array size field with the array field. |