Recipe12.5.Generating Pretty Printers


Recipe 12.5. Generating Pretty Printers

Problem

You need tools to help debug your application. In particular, you want the ability to render binary messages in human-readable form.

Solution

When developing messaging applications, developers often hand-code pretty printers because they make debugging these applications considerably easier. However, this kind of code can be generated if you have a message repository. This solution shows how to reuse the message switch generator from Recipe 12.2:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xslt [   <!--Used to control code intenting -->   <!ENTITY INDENT "    ">   <!ENTITY INDENT2 "&INDENT;&INDENT;">   <!ENTITY LS "&lt;&lt;"> ]> <xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">     <!-- This pretty-printer generator needs a message switch so we --> <!-- reuse the one we already wrote. --> <xsl:import href="messageSwitch.xslt"/>       <!--The directory to generate code -->   <xsl:param name="generationDir" select=" 'src/' "/>   <!--The C++ header file name -->   <xsl:param name="prettyPrintHeader" select=" 'prettyPrint.h' "/>   <!--The C++ source file name -->   <xsl:param name="prettyPrintSource" select=" 'prettyPrint.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" />        <xsl:template match="MessageRepository">     <xsl:document href="{concat($generationDir,$prettyPrintHeader)}">       <xsl:text>void prettyPrintMessage</xsl:text>       <xsl:text>(ostream&amp; stream, const Message&amp; msg);&#xa;</xsl:text>       <xsl:apply-templates select="DataTypes/Structure" mode="declare"/>     </xsl:document>         <xsl:document href="{concat($generationDir,$prettyPrintSource)}">       <xsl:apply-imports/>       <xsl:apply-templates select="DataTypes/Structure" mode="printers"/>     </xsl:document>          </xsl:template>          <!--Override the message processing function name from --> <!-- messageSwitch.xslt to customize the function --> <!-- signature to take a stream --> <xsl:template name="process-function"> <xsl:text>void prettyPrintMessage</xsl:text> <xsl:text>(ostream&amp; stream, const Message&amp; msg)</xsl:text> </xsl:template>     <!--Override case action from messageSwitch.xslt to generate --> <!-- call to prettyPrinter for message data --> <xsl:template name="case-action">  <xsl:text>   prettyPrint(stream, *static_cast&lt;const </xsl:text>  <xsl:value-of select="DataTypeName"/>  <xsl:text>*&gt;(msg.getData( ))) ;          break;</xsl:text> </xsl:template>     <!--Generate declarations for each message data type --> <xsl:template match="Structure" mode="declare"> <!--Forward declare the message data class --> <xsl:text>class </xsl:text> <xsl:value-of select="Name"/> <xsl:text> ;&#xa;</xsl:text> <!--Forward declare the message prettyPrint function --> <xsl:text>ostream prettyPrint(ostream &amp; stream, const </xsl:text> <xsl:value-of select="Name"/> <xsl:text>&amp; data);&#xa;</xsl:text> </xsl:template>     <!--Generate the body of a pretty-printer --> <xsl:template match="Structure" mode="printers"> <xsl:text>ostream prettyPrint(ostream &amp; stream, const </xsl:text> <xsl:value-of select="Name"/> <xsl:text>&amp; data)&#xa;</xsl:text> <xsl:text>{&#xa;</xsl:text> <xsl:text>&INDENT;stream &#xa;</xsl:text>   <xsl:text>&INDENT2;&LS; "</xsl:text> <xsl:value-of select="Name"/> <xsl:text>" &LS;  endl  &LS; "{"  &LS; endl &#xa;</xsl:text>     <xsl:for-each select="Members/Member">       <xsl:text>&INDENT2;&LS; "</xsl:text>       <xsl:value-of select="Name"/>: " &LS; <xsl:text/>       <xsl:apply-templates                   select="key('dataTypes',DataTypeName)" mode="print">         <xsl:with-param name="name" select="Name"/>       </xsl:apply-templates>        <xsl:text>&#xa;</xsl:text>    </xsl:for-each>   <xsl:text>&INDENT2;&LS; "}"  &LS; endl ; &#xa;</xsl:text>   <xsl:text>&INDENT;return stream ;&#xa;</xsl:text>   <xsl:text>}&#xa;&#xa;</xsl:text>  </xsl:template>     <!--Nested structures invoke the pretty-printer for that structure --> <xsl:template match="Structure" mode="print">   <xsl:param name="name"/>   <xsl:text>prettyPrint(stream, data.get_</xsl:text>   <xsl:value-of select="$name"/><xsl:text>( ))</xsl:text> </xsl:template>     <!--We assume there is a get function for each --> <!-- primitive component of the message --> <xsl:template match="*" mode="print">   <xsl:param name="name"/>   <xsl:text>data.get_</xsl:text>   <xsl:value-of select="$name"/>( ) &lt;&lt; endl<xsl:text/> </xsl:template>     </xsl:stylesheet>

The following source file is generated. We omit the header since it contains only declarations:

#include <messages/ADD_STOCK_ORDER.h> #include <messages/ADD_STOCK_ORDER_ACK.h> #include <messages/ADD_STOCK_ORDER_NACK.h> #include <messages/CANCEL_STOCK_ORDER.h> #include <messages/CANCEL_STOCK_ORDER_ACK.h> #include <messages/CANCEL_STOCK_ORDER_NACK.h> #include <messages/TEST.h>     #include <transport/Message.h> #include <transport/MESSAGE_IDS.h>             void prettyPrintMessage(ostream& stream, const Message& msg) {   switch (msg.getId( ))   {        case ADD_STOCK_ORDER_ID:          prettyPrint(stream, *static_cast<const           AddStockOrderData*>(msg.getData( ))) ;          break;     case ADD_STOCK_ORDER_ACK_ID:          prettyPrint(stream, *static_cast<const           AddStockOrderAckData*>(msg.getData( ))) ;          break;     case ADD_STOCK_ORDER_NACK_ID:          prettyPrint(stream, *static_cast<const           AddStockOrderNackData*>(msg.getData( ))) ;          break;     case CANCEL_STOCK_ORDER_ID:          prettyPrint(stream, *static_cast<const           CancelStockOrderData*>(msg.getData( ))) ;          break;     case CANCEL_STOCK_ORDER_ACK_ID:          prettyPrint(stream, *static_cast<const           CancelStockOrderAckData*>(msg.getData( ))) ;          break;     case CANCEL_STOCK_ORDER_NACK_ID:          prettyPrint(stream, *static_cast<const           CancelStockOrderNackData*>(msg.getData( ))) ;          break;     case TEST_ID:          prettyPrint(stream, *static_cast<const TestData*>(msg.getData( ))) ;          break;     return false ;   } }   ostream prettyPrint(ostream & stream, const TestData& data) {     stream          << "TestData" <<  endl  << "{"  << endl          << "order: " << prettyPrint(stream, data.get_order( ))         << "cancel: " << prettyPrint(stream, data.get_cancel( ))         << "}"  << endl ;      return stream ; }     ostream prettyPrint(ostream & stream, const AddStockOrderData& data) {     stream          << "AddStockOrderData" <<  endl  << "{"  << endl          << "symbol: " << data.get_symbol( ) << endl         << "quantity: " << data.get_quantity( ) << endl         << "side: " << data.get_side( ) << endl         << "type: " << data.get_type( ) << endl         << "price: " << data.get_price( ) << endl         << "}"  << endl ;      return stream ; }     ostream prettyPrint(ostream & stream, const AddStockOrderAckData& data) {     stream          << "AddStockOrderAckData" <<  endl  << "{"  << endl          << "orderId: " << data.get_orderId( ) << endl         << "}"  << endl ;      return stream ; }     ostream prettyPrint(ostream & stream, const AddStockOrderNackData& data) {     stream          << "AddStockOrderNackData" <<  endl  << "{"  << endl          << "reason: " << data.get_reason( ) << endl         << "}"  << endl ;      return stream ; }     ostream prettyPrint(ostream & stream, const CancelStockOrderData& data) {     stream          << "CancelStockOrderData" <<  endl  << "{"  << endl          << "orderId: " << data.get_orderId( ) << endl         << "quantity: " << data.get_quantity( ) << endl         << "}"  << endl ;      return stream ; }     ostream prettyPrint(ostream & stream, const CancelStockOrderAckData& data) {     stream          << "CancelStockOrderAckData" <<  endl  << "{"  << endl          << "orderId: " << data.get_orderId( ) << endl         << "quantityRemaining: " << data.get_quantityRemaining( ) << endl         << "}"  << endl ;      return stream ; }     ostream prettyPrint(ostream & stream, const CancelStockOrderNackData& data) {     stream          << "CancelStockOrderNackData" <<  endl  << "{"  << endl          << "orderId: " << data.get_orderId( ) << endl         << "reason: " << data.get_reason( ) << endl         << "}"  << endl ;      return stream ; }

Discussion

This code-generation recipe attacks the pretty-printing problem head on by literally generating the pretty-print code for each message. Following this example is simple, and the results are effective. However, you could approach the problem more generally, and in the process create a more useful code generator.

Specifically, you can break the pretty-printing process into two stages. One stage is the process of parsing a monolithic message into its constituent parts. The other is the process of taking those parts and formatting them into human-readable text.

Looking at the problem in this way changes the solution from the generation of a single-purpose set of functions (a pretty printer) to the generation of a more generic message parser. Such parsers are usually event driven. Readers familiar with the Simple API for XML (SAX) will recognize this style of processing. The stylesheet used to generate a message parser is a variation of the pretty-print generator. Instead of sending message components to a stream, it sends parse events to a handler:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xslt [   <!--Used to control code intenting -->   <!ENTITY INDENT "    ">   <!ENTITY INDENT2 "&INDENT;&INDENT;">   <!ENTITY LS "&lt;&lt;"> ]> <xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">     <!-- This message parse generator needs a message switch so we --> <!-- reuse the one we already wrote. --> <xsl:import href="messageSwitch.xslt"/>       <!--The directory to generate code -->   <xsl:param name="generationDir" select=" 'src/' "/>   <!--The C++ header file name -->   <xsl:param name="msgParseHeader" select=" 'msgParse.h' "/>   <!--The C++ source file name -->   <xsl:param name="msgParseSource" select=" 'msgParse.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" />        <xsl:template match="MessageRepository">     <xsl:document href="{concat($generationDir,$msgParseHeader)}">       <xsl:text>void parseMessage</xsl:text>        <xsl:text>(MessageHandler&amp; handler, const Message&amp; msg);&#xa;        </xsl:text>       <xsl:apply-templates select="DataTypes/Structure" mode="declare"/>     </xsl:document>         <xsl:document href="{concat($generationDir,$msgParseSource)}">       <xsl:apply-imports/>       <xsl:apply-templates select="DataTypes/Structure" mode="parsers"/>     </xsl:document>          </xsl:template>          <!--Override the message-processing function name from --> <!-- messageSwitch.xslt to customize the function signature -->  <!-- to take a handler --> <xsl:template name="process-function"> <xsl:text>void parseMessage</xsl:text> <xsl:text>(MessageHandler&amp; handler, const Message&amp; msg)</xsl:text> </xsl:template>     <!--Override case action from messageSwitch.xslt to generate --> <!-- call to parse for message data --> <xsl:template name="case-action">  <xsl:text>   parse(handler, *static_cast&lt;const </xsl:text>  <xsl:value-of select="DataTypeName"/>  <xsl:text>*&gt;(msg.getData( ))) ;          break;</xsl:text> </xsl:template>     <!--Generate declarations for each message data type --> <xsl:template match="Structure" mode="declare"> <!--Forward declare the message data class --> <xsl:text>class </xsl:text> <xsl:value-of select="Name"/> <xsl:text> ;&#xa;</xsl:text> <!--Forward declare the message parse function --> <xsl:text>void parse(MessageHandler &amp; handler, const </xsl:text> <xsl:value-of select="Name"/> <xsl:text>&amp; data);&#xa;</xsl:text> </xsl:template>     <!--Generate the body of a parser --> <xsl:template match="Structure" mode="parsers"> <xsl:text>void parse(MessageHandler &amp; handler, const </xsl:text> <xsl:value-of select="Name"/> <xsl:text>&amp; data)&#xa;</xsl:text> <xsl:text>{&#xa;</xsl:text> <xsl:text>&INDENT;handler.beginStruct("</xsl:text>   <xsl:value-of select="Name"/> <xsl:text>") ;&#xa;</xsl:text>     <xsl:for-each select="Members/Member">       <xsl:apply-templates             select="key('dataTypes',DataTypeName)" mode="parse">         <xsl:with-param name="name" select="Name"/>       </xsl:apply-templates>    </xsl:for-each> <xsl:text>&INDENT;handler.endStruct("</xsl:text>   <xsl:value-of select="Name"/> <xsl:text>") ;&#xa;</xsl:text>     <xsl:text>}&#xa;&#xa;</xsl:text>  </xsl:template>     <!--Nested structures invoke the parser for that structure --> <xsl:template match="Structure" mode="parse">   <xsl:param name="name"/>   <xsl:text>&INDENT;parse(handler, data.get_</xsl:text>   <xsl:value-of select="$name"/><xsl:text>( ));&#xa;</xsl:text> </xsl:template>     <!--We assume there is a get function for each --> <!-- primitive component of the message --> <xsl:template match="*" mode="parse">   <xsl:param name="name"/>   <xsl:text>&INDENT;handler.field("</xsl:text>   <xsl:value-of select="$name"/>","<xsl:text/>   <xsl:value-of select="Name"/>",<xsl:text/>   <xsl:text>data.get_</xsl:text>   <xsl:value-of select="$name"/>( )<xsl:text/>   <xsl:text>);&#xa;</xsl:text>  </xsl:template>     </xsl:stylesheet>

It produces parse functions that look like the following code:

void parse(MessageHandler & handler, const AddStockOrderData& data) {     handler.beginStruct("AddStockOrderData") ;     handler.field("symbol","StkSymbol",data.get_symbol( ));     handler.field("quantity","Shares",data.get_quantity( ));     handler.field("side","BuyOrSell",data.get_side( ));     handler.field("type","OrderType",data.get_type( ));     handler.field("price","Real",data.get_price( ));     handler.endStruct("AddStockOrderData")  ; }




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net