Главная » XSLT » Генерация функций форматированной печати

0

Задача

Необходимы инструменты для отладки приложения. В частности, хотелось бы иметь возможность распечатывать двоичные сообщения в форме, понятной человеку.

Решение

При разработке приложений, обрабатывающих сообщения, программисты ча­сто вручную пишут функции для их распечатки, поскольку это заметно упрощает отладку. Однако при наличии репозитория сообщений такой код можно сгенери­ровать автоматически. В этом решении показано, как можно повторно использо­вать генератор предложения switch из рецепта 12.2.

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xslt [

<!– Служит для yпpaвления отступами –> <!ENTITY INDENT "   ">

<!ENTITY INDENT2 "&INDENT;&INDENT;"> <!ENTITY LS "&lt;&lt;">

]>

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!— Для этого ^^pa^pa функций печати нужно ветвление по типу сообщения, —> <!– поэтому пoвтopнo используем написанное paнее. –> <xsl:import href="messageSwitch.xslt"/>

<!– Каталог, в кoтopoм xpaнятся сгенеpиpoвaнные файлы —> <xsl:param name="generationDir" select=" ‘src/’ "/> <!– Имя заголовочного файла C++ –>

<xsl:param name="prettyPrintHeader" select=" ‘prettyPrint.h’ "/> <!– Имя исходного файла C++ –>

<xsl:param name="prettyPrintSource" select=" ‘prettyPrint.C’ "/>

<!– Ключ для поиска типов данных по имени –> <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>

<!– Переопределяем имя функции обработки сообщения в шаблоне –> <!– из таблицы messageSwitch.xslt, так что теперь в ее сигнатуре –> <!– указано, что на вход подается поток –> <xsl:template name="process-function"> <xsl:text>void prettyPrintMessage</xsl:text>

<xsl:text>(ostream&amp; stream, const Message&amp; msg)</xsl:text> </xsl:template>

<!– Переопределяем действие в ветви case в шаблоне из таблицы –> <!– messageSwitch.xslt, так чтобы вызывалась функция prettyPrinter –> <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>

<!– Генерируем объявления для каждого типа сообщений –> <xsl:template match="Structure" mode="declare">

<!– Опережающее объявление класса, представляющего данные сообщения –> <xsl:text>class </xsl:text> <xsl:value-of select="Name"/> <xsl:text> ;&#xa;</xsl:text>

<!– Опережающее объявление функции prettyPrint для печати сообщения –> <xsl:text>ostream prettyPrint(ostream &amp; stream, const </xsl:text> <xsl:value-of select="Name"/> <xsl:text>&amp; data);&#xa;</xsl:text> </xsl:template>

<!– Генерируем тело функции печати –> <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>

<!– При наличии вложенной структуры для нее вызывается функция печати –> <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>

<!– Предполагается, что для каждого примитивного компонента сообщения –> <!– имеется метод get — > <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>

В результате генерируется следующий исходный файл. Заголовочный файл мы опустили, так как он содержит только объявления.

#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 Messages 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 << "orderld: " << data.get_orderId( ) << endl << "reason: " << data.get_reason( ) << endl << "}" << endl ; return stream ;

}

Обсуждение

В этом рецепте мы генерируем код печати для каждого сообщения. Понять, как это делается, несложно, а результирующий код достаточно эффективен. Но можно было бы подойти к решению задачи с более общих позиций и создать более полезный генератор кода.

Точнее, процесс печати можно разбить на два этапа. На первом этапе монолит­ное сообщение разбивается на составные части. На втором этапе каждая часть преобразуется в понятный человеку текст.

Если взглянуть на задачу под этим углом зрения, то вместо генерации набора функций для выполнения одной узкой задачи (форматированной печати) можно сгенерировать более общий анализатор сообщений. Обычно такие анализаторы управляются событиями. Читатель, знакомый с интерфейсом SAX (Simple API for XML), легко опознает знакомый стиль обработки. Таблица стилей для генерации анализатора сообщений – это вариация на тему генератора функций печати. Но вместо того, чтобы отправлять компоненты сообщения в поток, она передает со­бытия разбора обработчику:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xslt [

<!– Служит для управления отступами –> <!ENTITY INDENT "   ">

<!ENTITY INDENT2 "&INDENT;&INDENT;"> <!ENTITY LS "&lt;&lt;">

]>

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!– Для этого генератора программы разбора сообщений нужно ветвление –> <!– по типу сообщения, поэтому повторно используем написанное ранее. –> <xsl:import href="messageSwitch.xslt"/>

<!— Каталог, в котором хранятся сгенерированные файлы —>

<xsl:param name="generationDir" select=" ‘src/’ "/> <!– Имя заголовочного файла C++ –>

<xsl:param name="msgParseHeader" select=" ‘msgParse.h’ "/> <!– Имя исходного файла C++ –>

<xsl:param name="msgParseSource" select=" ‘msgParse.C’ "/>

<!– Ключ для поиска типов данных по имени –> <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>

<!– Переопределяем имя функции обработки сообщения в шаблоне –> <!– из таблицы messageSwitch.xslt, так что теперь в ее сигнатуре –> <!– указано, что ей передается обработчик –> <xsl:template name="process-function"> <xsl:text>void parseMessage</xsl:text>

<xsl:text>(MessageHandler&amp; handler, const Message&amp; msg)</xsl:text> </xsl:template>

<!– Переопределяем действие в ветви case в шаблоне из таблицы –> <!– messageSwitch.xslt, так чтобы вызывалась функция разбора –> <!– данных сообщения –> <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>

<!– Генеpиpyем объявления для каждого типа сообщений –> <xsl:template match="Structure" mode="declare">

<!– Опеpежaющее объявление класса, пpедстaвляющегo данные сообщения –> <xsl:text>class </xsl:text> <xsl:value-of select="Name"/> <xsl:text> ;&#xa;</xsl:text>

<!– Опеpежaющее объявление функции paзбopa сообщения –> <xsl:text>void parse(MessageHandler &amp; handler, const </xsl:text> <xsl:value-of select="Name"/> <xsl:text>&amp; data);&#xa;</xsl:text> </xsl:template>

<!– Генеpиpyем тело функции paзбopa сообщения –> <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>

<!— Пpи наличии вложенной стpyктypы для нее вызывается функция paзбopa —> <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>

<!– Пpедпoлaгaется, что для каждого пpимитивнoгo компонента сообщения –> <!– имеется метод get –> <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>

В результате генерируются функции разбора такого вида:

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. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии