Pretty Printer generieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie benötigen unterstützende Werkzeuge bei der Suche nach Fehlern in Ihrer Anwendung. Insbesondere müssen Sie Nachrichten in Binärform in einer für Menschen lesbaren Form darstellen.

Lösung

Bei der Entwicklung von nachrichtenbasierten Anwendungen kodieren Entwickler oftmals eigene Pretty Printer von Hand, weil diese die Fehlersuche bei solchen Anwendungen erheblich vereinfachen. Aber wenn Sie über ein Nachrichten-Repository verfügen, können Sie diese Art von Code auch generieren. Diese Lösung demonstriert, wie Sie dazu den Generator des Nachrichten-Switchs aus dem Rezept Switch-Code generieren verwenden können:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xslt [
  <!-- Wird für die Steuerung der Code-Einrückung benutzt -->
  <!ENTITY INDENT "    ">
  <!ENTITY INDENT2 "&INDENT;&INDENT;">
  <!ENTITY LS "&lt;&lt;">
]>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- Dieser Pretty-Printer-Generator benötigt einen Nachrichten-Switch, also verwenden wir den, den wir schon geschrieben haben. -->
  <xsl:import href="messageSwitch.xslt"/>
  <!-- Das Zielverzeichnis des generierten Codes -->
  <xsl:param name="generationDir" select=" 'src/' "/>
  <!-- Der C++-Headerdateiname -->
  <xsl:param name="prettyPrintHeader" select=" 'prettyPrint.h' "/>
  <!-- Der C++-Quelldateiname -->
  <xsl:param name="prettyPrintSource" select=" 'prettyPrint.C' "/>
  <!-- Ein Schlüssel, um Datentypen über den Namen zu finden -->
  <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>
  <!-- Überschreibe den Namen der nachrichtenverarbeitenden Funktion von messageSwitch.xslt, um die Funktionssignatur so anzupassen, dass sie einen Datenstrom akzeptiert. -->
  <xsl:template name="process-function">
    <xsl:text>void prettyPrintMessage</xsl:text>
    <xsl:text>(ostream&amp; stream, const Message& msg)</xsl:text>
  </xsl:template>
  <!-- Überschreibe die case-Aktion aus messageSwitch.xslt, um einen Aufruf von prettyPrinter für die Nachrichtendaten zu generieren. -->
  <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>
  <!-- Generiere Deklarationen für jeden Nachrichten-Datentyp -->
  <xsl:template match="Structure" mode="declare">
    <!-- Vorwärtsdeklaration der Message-Datenklasse -->
    <xsl:text>class </xsl:text>
    <xsl:value-of select="Name"/>
    <xsl:text> ;&#xa;</xsl:text>
    <!-- Vorwärtsdeklaration der Message-prettyPrint-Funktion -->
    <xsl:text>ostream prettyPrint(ostream &amp; stream, const </xsl:text>
    <xsl:value-of select="Name"/>
    <xsl:text>&amp; data);&#xa;</xsl:text>
  </xsl:template>
  <!-- Generiere den Rumpf eines pretty-printers -->
  <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>
  <!-- Verschachtelte Strukturen rufen den pretty-printer für diese Struktur auf -->
  <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>
  <!-- Wir nehmen an, es gibt eine get-Funktion für alle primitiven Komponenten der Nachrichten -->
  <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>

Folgende Quelldatei wird generiert. Wir lassen den Header hier weg, weil er ohnehin nur Deklarationen enthält:

#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 ;
}

Diskussion

Dieses Code-Generierungsrezept packt das Pretty Printing-Problem direkt an der Wurzel, indem es den Pretty Print-Code für alle Nachrichten wortwörtlich generiert. Man kann diesem Beispiel leicht folgen und bekommt wirksame Resultate. Sie könnten das Problem aber auch allgemeiner angehen und dabei einen noch nützlicheren Code-Generator erzeugen.

Ganz konkret könnten Sie den Pretty Printing-Vorgang in zwei Stufen aufteilen. Die eine Stufe besteht aus dem Parsen und Zerlegen einer monolithischen Nachricht in ihre Bestandteile. Die nächste Stufe besteht darin, diese Teile zu nehmen und in eine für Menschen lesbare Textform zu bringen.

Wenn Sie das Problem auf diese Weise betrachten, ändert sich die Lösung von der Generierung einer Menge von Funktionen mit einem einzigen bestimmten Zweck (einem Pretty Printer eben) zur Generierung eines allgemeineren Nachrichten-Parsers. Solche Parser werden normalerweise von Ereignissen gesteuert. Leser, die mit SAX (Simple API for XML) vertraut sind, werden diese Art von Verarbeitung hier wiedererkennen. Das Stylesheet, mit dem ein Nachrichten-Parser generiert wird, ist eine Variante des Pretty Print-Generators. Anstatt Nachrichten-Bestandteile an einen Strom zu schicken, sendet es Parse-Ereignisse an einen Handler:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xslt [
  <!-- Wird für die Steuerung der Code-Einrückung benutzt -->
  <!ENTITY INDENT "    ">
  <!ENTITY INDENT2 "&INDENT;&INDENT;">
  <!ENTITY LS "&lt;&lt;">
]>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- Dieser Nachrichten-Parser-Generator benötigt einen Nachrichten-Switch, also verwenden wir den, den wir schon geschrieben haben. -->
  <xsl:import href="messageSwitch.xslt"/>
  <!-- Das Zielverzeichnis des generierten Codes -->
  <xsl:param name="generationDir" select=" 'src/' "/>
  <!-- Der C++-Headerdateiname -->
  <xsl:param name="msgParseHeader" select=" 'msgParse.h' "/>
  <!-- Der C++-Quelldateiname -->
  <xsl:param name="msgParseSource" select=" 'msgParse.C' "/>
  <!-- Ein Schlüssel, um Datentypen über den Namen zu finden. -->
  <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>
  <!-- Überschreibe den Namen der nachrichtenverarbeitenden Funktion von messageSwitch.xslt, um die Funktionssignatur so anzupassen, dass sie einen Handler akzeptiert. -->
  <xsl:template name="process-function">
    <xsl:text>void parseMessage</xsl:text>
    <xsl:text>(MessageHandler&amp; handler, const Message&amp; msg)</xsl:text>
  </xsl:template>
  <!-- Überschreibe die case-Aktion aus messageSwitch.xslt, um einen Aufruf zum Parsen der Nachrichtendaten zu generieren. -->
  <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>
  <!-- Generiere Deklarationen für jeden Nachrichten-Datentyp -->
  <xsl:template match="Structure" mode="declare">
    <!-- Vorwärtsdeklaration der Nachrichten-Datenklasse -->
    <xsl:text>class </xsl:text>
    <xsl:value-of select="Name"/>
    <xsl:text> ;&#xa;</xsl:text>
    <!-- Vorwärtsdeklaration der Nachrichten-Parse-Funktion -->
    <xsl:text>void parse(MessageHandler &amp; handler, const </xsl:text>
    <xsl:value-of select="Name"/>
    <xsl:text>&amp; data);&#xa;</xsl:text>
  </xsl:template>
  <!-- Generiere den Rumpf eines Parsers -->
  <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>
  <!-- Verschachtelte Strukturen rufen den Parser für diese Struktur auf. -->
  <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>
  <!-- Wir nehmen an, dass es eine get-Funktion für alle primitiven Komponenten der Nachricht gibt -->
  <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>

Das Stylesheet produziert Parse-Funktionen, die ähnlich wie folgender Code aussehen:

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") ;
}

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de