The 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 gnerate 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 Stucture. 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 Stucture. --> <!-- 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 ; } ;
#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; }
This 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 with out 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.