Previous Page Next Page

Recipe 12.1. Generating Constant Definitions

Problem

You want to generate a source file containing all message names as constant equivalent to their message IDs.

Solution

You can construct a single transformation that uses C++ as the default target but is easily customized for C, C#, or Java:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
  <xsl:output method="text"/>
  <xsl:strip-space elements="*"/>
   
  <!--The name of the output source code file. --> 
  <xsl:param name="file" select=" 'MESSAGE_IDS.h' "/>
  
  <!-- The default behavior is to generate C++ style constants -->
  <xsl:variable name="constants-type" select=" 'const int' "/>
   
  <!-- The default C++ assignment operator -->
  <xsl:variable name="assignment" select=" ' = ' "/>
   
  <!-- The default C++ statement terminator -->
  <xsl:variable name="terminator" select=" ';' "/>
   
   
  <!--Transform repository into a sequence of message constant 
      definitions -->  
  <xsl:template match="MessageRepository">
    <xsl:call-template name="constants-start"/>
    <xsl:apply-templates select="Messages/Message"/>
    <xsl:call-template name="constants-end"/>
  </xsl:template>  
   
  <!--Each meesage becomes a comment and a constant definition -->
  <xsl:template match="Message">
    <xsl:apply-templates select="." mode="doc" />
    <xsl:apply-templates select="." mode="constant" />
  </xsl:template>
   
  <!-- C++ header files start with an inclusion guard -->
  <xsl:template name="constants-start">
    <xsl:variable name="guard" select="translate($file,'.','_')"/>
    <xsl:text>#ifndef </xsl:text>
    <xsl:value-of select="$guard"/>
    <xsl:text>&#xa;</xsl:text> 
    <xsl:text>#define </xsl:text>
    <xsl:value-of select="$guard"/>
    <xsl:text>&#xa;&#xa;&#xa;</xsl:text>
  </xsl:template>
   
  <!-- C++ header files end with the closure of the top-level inclusion 
       guard -->
  <xsl:template name="constants-end">
    <xsl:variable name="guard" select="translate($file,'.','_')"/>
    <xsl:text>&#xa;&#xa;&#xa;#endif /* </xsl:text>
    <xsl:value-of select="$guard"/>
    <xsl:text> */&#xa;</xsl:text> 
  </xsl:template>
   
  <!-- Each constant definition is preceded by a comment describing the 
       associated message -->
  <xsl:template match="Message" mode="doc">
  /*
  * Purpose:      <xsl:call-template name="format-comment"> 
                        <xsl:with-param name="text" select="Documentation"/>
                        </xsl:call-template>
  * Data Format: <xsl:value-of select="DataTypeName"/>
  * From:        <xsl:apply-templates select="Senders" mode="doc"/>
  * To:          <xsl:apply-templates select="Receivers" mode="doc"/>
  */
  </xsl:template>
   
  <!-- Used in the generation of message documentation. Lists sender or
       receiver processes -->
  <xsl:template match="Senders|Receivers" mode="doc">
    <xsl:for-each select="ProcessRef">
      <xsl:value-of select="."/>
      <xsl:if test="position( ) != last( )">
       <xsl:text>, </xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
   
  <!-- This utility wraps comments at 40 characters wide -->
  <xsl:template name="format-comment">
    <xsl:param name="text"/>
    <xsl:choose>
      <xsl:when test="string-length($text)&lt;40">
        <xsl:value-of select="$text"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="substring($text,1,39)"/>
        <xsl:text>*&#xa;</xsl:text>
        <xsl:call-template name="format-comment">
          <xsl:with-param name="text" select="substring($text,40)"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
   
  <!-- Each message name becomes a constant whose value is the message 
       id -->
  <xsl:template match="Message" mode="constant">
    <xsl:value-of select="$constants-type"/><xsl:text> </xsl:text>
    <xsl:value-of select="Name"/>
    <xsl:value-of select="$assignment"/>
    <xsl:value-of select="MsgId"/>
    <xsl:value-of select="$terminator"/>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>
  
  <!-- Ignore text nodes not explicitly handled by above templates -->
  <xsl:template match="text( )"/>
  
</xsl:stylesheet>

When run against your repository, this transform generates the following code:

#ifndef MESSAGE_IDS_h
#define MESSAGE_IDS_h
   
   
  /*
  * Purpose:     Add a new order.
  * Data Format: AddStockOrderData
  * From:        StockClient
  * To:          StockServer
  */
  const int ADD_STOCK_ORDER_ID = 1;
   
  /*
  * Purpose:     Acknowledge the order has been added.
  * Data Format: AddStockOrderAckData
  * From:        StockServer
  * To:          StockClient
  */
  const int ADD_STOCK_ORDER_ACK_ID = 2;
   
  /*
  * Purpose:     Error adding the order. Perhaps it violates
  *              a rule.
  * Data Format: AddStockOrderNackData
  * From:        StockServer
  * To:          StockClient
  */
  const int ADD_STOCK_ORDER_NACK_ID = 3;
   
//Etc ...
   
#endif /* MESSAGE_IDS_h */

Discussion

To make the code-generation transformation customizable for several languages, I use a stylesheet that is more complex than necessary for any single language. Still, this chapter did not generalize it completely. For example, the commenting conventions assume the language is in the C ancestry. The content of the comments also may not suit your particular style or taste. However, as you create your own code-generation templates, you should apply these customization techniques:

  • Encode language-specific constructs in top-level parameters or variables so they can be overridden by importing stylesheets or (if you use parameters) by passing in parameter values when the stylesheet is run.

  • Break the various generated components into separate templates that can be overridden individually by importing stylesheets.

Having designed the transformation in this way allows C-style #define constants to be generated with only minor changes:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
  <xsl:import href="msgIds.xslt"/>
  
  <xsl:variable name="constants-type" select=" '#define ' "/>
  <xsl:variable name="assignment" select=" '   ' "/>
  <xsl:variable name="terminator" select=" '' "/>
  
</xsl:stylesheet>

Java requires everything to live inside a class, but you can accommodate that too:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
<xsl:import href="msgIds.xslt"/>
   
 <xsl:variable name="constants-type" select=" 'public static final int' "/>
   
  <xsl:template name="constants-start">
  <xsl:text>final public class MESSAGE_IDS &#xa;</xsl:text> 
  <xsl:text>{&#xa;</xsl:text>
  </xsl:template>
   
  <xsl:template name="constants-end">
  <xsl:text>&#xa;&#xa;}&#xa;</xsl:text> 
  </xsl:template>
   
</xsl:stylesheet>


Previous Page Next Page