Автоматизация – это Святой Грааль в разработке программного обеспечения. Вообще, прогресс в этой области в значительной мере обязан идее генерации кода по той или иной спецификации. Разве не этим занимаются ассемблеры и компиляторы? Но есть и другой вид генерации кода, когда целью является не исполняемый машинный код, а программа на языке высокого уровня, например, Java или C++. Зачем это может понадобиться и какое к этому отношение имеет XML?
При написании программ вы записываете различные знания на языке со специфическими синтаксическими правилами, оптимизированном для одного конкретного этапа жизненного цикла разработки. Проделанной работой трудно воспользоваться для выполнения других важных задач разработки, поскольку синтаксический разбор языков программирования сложен, а наиболее интересная информация упрятана в неформализованных комментариях. Представление знаний о приложении в формате XML открывает больше возможностей для ее использования в разных местах. Имея спецификацию, составленную на XML, вы можете сгенерировать код самого приложения, тестовые программы, документацию и, возможно, даже тестовые данные. Я не хочу сказать, что XML дает все это задаром. Как и всегда при разработке программного обеспечения, нужно сначала все тщательно спланировать, выстроить инфраструктуру, а потом уже пожинать плоды.
Эта глава отличается от остальных тем, что большинство примеров в ней – компоненты решения в контексте конкретного приложения. Для такого построения есть две причины.
Во-первых, маловероятно, что вы будете кодировать информацию в формате XML для последующей генерации кода только потому, что XML – это «круто». Как правило, предстоит решить некую крупную задачу, в которой XML можно использовать и для других целей. Поэтому и примеры в этом разделе станут более осмысленными, если представить их в контексте объемлющей задачи.
Во-вторых, эта объемлющая задача обычно возникает при разработке крупномасштабного приложения, что само по себе может заинтересовать читателя. Но даже если это не так, включение в рассмотрение объемлющей задачи не заведет нас слишком далеко от применения излагаемых идей к прочим этапам разработки.
Итак, в чем же состоит объемлющая задача?
Представим себе сложное клиент-серверное приложение. Говоря сложное, я имею в виду, что оно состоит из многочисленных процессов на стороне сервера и клиента. Эти процессы обмениваются между собой данными в виде сообщений, передаваемых с помощью связующего программного обеспечения для передачи сообщений (от точки к точке, типа публикация/подписка или то и другое). В качестве примеров таких программ можно назвать IBM MQSeries, Microsoft Message Queuing (MSMQ), BEA Systems Tuxedo и TIBCO Rendezvous. Для нас неважно, какой конкретно продукт используется. А важно то, что основную работу система выполняет при получении сообщения, в ответ на которое должна послать в ответ одно или несколько сообщений1. Сообщение может содержать XML (в виде SOAP), неразмеченный текст или двоичные данные. В главе 12 мы рассмотрим протокол SOAP в контексте WSDL. А сейчас нас интересуют межсерверные коммуникации, в которых XML применяется реже.
В таких сложных системах плохо то, что в них невозможно разобраться, просто изучив исходный текст какого-то одного компонента. Сначала нужно понять, как устроен обмен данными между процессами, или межпроцессные протоколы сообщений. Мы пойдем даже дальше и скажем, что в первом приближении детали отдельных процессов вообще несущественны. Можно рассматривать каждый процесс как черный ящик. И тогда, вместо того чтобы читать сотни тысяч строк кода, составляющего систему, можно начать ее изучение с анализа относительно небольшого набора сообщений, которыми обмениваются процессы.
Но тут же возникает следующий вопрос: как разобраться в языке межпроцессного общения в сложном приложении? Есть ли какое-то одно место, где можно почерпнуть всю информацию? Увы, зачастую это не так. Очень редко удается найти актуальную и полную спецификацию прикладных протоколов. Отыскать кусочки головоломки можно в различных заголовочных файлах и в проектной документации, разрабатывавшейся на протяжении жизненного цикла системы, но единого места, в котором была бы собрана вся жизненно важная информация, не существует. Во многих случаях единственный надежный способ получить ее – изучить исходный код, от чего я как раз и предостерегал в самом начале!
Ну хорошо, а какое отношение все это имеет к XML, XSLT и, главное, к генерации кода? Собственно, проблема состоит в отсутствии документации, детально описывающей структуру межпроцессных сообщений в приложении. Как мог бы выглядеть такой документ? Разработчики могли бы поддерживать в актуальном состоянии созданный в MS Word документ, в котором описаны все сообщения. Еще лучше, если бы этой теме был посвящен специальный сайт, поддерживающий навигацию и поиск. А, быть может (и вы, конечно, сами догадались!), информацию стоит хранить в XML-файле! Тогда из этого файла можно было бы сгенерировать и сайт. И уж раз такой файл имеется, то почему бы не попытаться сгенерировать хотя бы частично код, необходимый для обработки описанных сообщений. Именно этим мы и займемся в настоящей главе. Я буду называть множество XML-файлов репозиторием межпроцессных сообщений. В рецептах ниже демонстрируется, как генерировать код на основе такого репозитория.
Но, прежде чем переходить к рецептам, поговорим о структуре репозито- рия. Формально она описывается в виде схемы W3C XSD Schema, но мы представим только интуитивно понятный рисунок для тех, кто со схемами XML не знаком.
Рис. 12.1 был создан программой Altova’s XML Spy 4.0 (http://www.xmlspy.com). Значок троеточия обозначает упорядоченную последовательность. Значок, напоминающий трехпозиционный переключатель, обозначает выбор.
Хотя этой схемы и достаточно для иллюстрации интересных рецептов генерации кода, до промышленного репозитория сообщений она не дотягивает. В репо- зитории можно было бы хранить следующие дополнительные данные:
? символические константы, описывающие размеры массивов и строк, а также значения, употребляемые в перечислениях;
? информацию о сложных типах данных, например, объединениях и псевдонимах типов (typedef в языке C);
? информацию о протоколах (последовательностях сообщений, которыми процессы обмениваются для достижения конкретного результата);
? информацию об авторах оригинальной версии, датах и авторах изменений и т.п.;
? информацию о доставке и транспортировке: имена издателей и подписчиков либо имена очередей.
В качестве примера рассмотрим простое клиент-серверное приложение, которое позволяет отправлять заказы на склад или отменять их. Репозиторий для подобного приложения мог бы выглядеть так:
<MessageRepository xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:
noNamespaceSchemaLocation="C:\MyProjects\XSLT Cookbook\code
gen\MessageRepository.xsd"> <DataTypes> <Primitive>
<Name>Real</Name> <Size>8</Size> <Category>real</Category> </Primitive> <Primitive>
<Name>Integer</Name>
Рис. 12.1. Графическое представление XSD-схемы репозитория
<Size>4</Size>
<Category>signed integer</Category> с/Primitive> Primitive> <Name>StkSymbol</Name> <Size>10</Size> <Category>string</Category> с/Primitive> Primitive> <Name>Message</Name> <Size>100</Size> <Category>string</Category> с/Primitive>
<Primitive>
<Name>Shares</Name> <Size>4</Size>
<Category>signed integer</Category> </Primitive> <Enumeration>
<Name>BuyOrSell</Name> <Enumerators> <Enumerator>
<Name>BUY</Name> <Value>0</Value> </Enumerator> <Enumerator>
<Name>SELL</Name> <Value>1</Value> </Enumerator> </Enumerators> </Enumeration> <Enumeration>
<Name>OrderType</Name> <Enumerators> <Enumerator>
<Name>MARKET</Name> <Value>0</Value> </Enumerator> <Enumerator>
<Name>LIMIT</Name> <Value>1</Value> </Enumerator> </Enumerators> </Enumeration> <Structure>
<Name>TestData</Name> <Members> <Member>
<Name>order</Name>
<DataTypeName>AddStockOrderData</DataTypeName> </Member> <Member>
<Name>cancel</Name>
<DataTypeName>CancelStockOrderData</DataTypeName> </Member> </Members> </Structure>
<Name>AddStockOrderData</Name>
<Documentation>Запрос на добавление нового заказа.</Documentation> <Members> <Member>
<Name>symbol</Name>
<DataTypeName>StkSymbol</DataTypeName> </Member> <Member>
<Name>quantity</Name> <DataTypeName>Shares</DataTypeName> </Member> <Member>
<Name>side</Name>
<DataTypeName>BuyOrSell</DataTypeName> </Member> <Member>
<Name>type</Name>
<DataTypeName>OrderType</DataTypeName> </Member> <Member>
<Name>price</Name>
<DataTypeName>Real</DataTypeName> </Member> </Members> </Structure> <Structure>
<Name>AddStockOrderAckData</Name>
<Documentation>Подтверждение успешного добавления заказа. </Documentation> <Members> <Member>
<Name>orderId</Name>
<DataTypeName>Integer</DataTypeName> </Member> </Members> </Structure> <Structure>
<Name>AddStockOrderNackData</Name>
<Documentation>Cообщение об ошибке при добавлении заказа. </Documentation> <Members> <Member>
<Name>reason</Name>
<DataTypeName>Message</DataTypeName> </Member> </Members> </Structure> <Structure>
<Name>CancelStockOrderData</Name>
<Documentation>3anpoc на полную или частичную отмену заказа</ Documentation> <Members> <Member>
<Name>orderId</Name>
<DataTypeName>Integer</DataTypeName> </Member> <Member>
<Name>quantity</Name> <DataTypeName>Shares</DataTypeName> </Member> </Members> </Structure> <Structure>
<Name>CancelStockOrderAckData</Name>
<Documentation>Пoдтвepждeниe успешной отмены заказа. </Documentation> <Members> <Member>
<Name>orderId</Name>
<DataTypeName>Integer</DataTypeName> </Member> <Member>
<Name>quantityRemaining</Name> <DataTypeName>Shares</DataTypeName> </Member> </Members> </Structure> <Structure>
<Name>CancelStockOrderNackData</Name>
<Documentation>Cooбщeниe об ошибке при отмена зaкaзa.</Documentation> <Members> <Member>
<Name>orderId</Name>
<DataTypeName>Integer</DataTypeName> </Member> <Member>
<Name>reason</Name>
<DataTypeName>Message</DataTypeName> </Member> </Members> </Structure> </DataTypes> <Messages> <Message>
<Name>ADD_STOCK_ORDER</Name> <MsgId>1</MsgId>
<DataTypeName>AddStockOrderData</DataTypeName> <Senders>
<ProcessRef>StockClient</ProcessRef> </Senders> <Receivers>
<ProcessRef>StockServer</ProcessRef> </Receivers> </Message> <Message>
<Name>ADD_STOCK_ORDER_ACK</Name> <MsgId>2</MsgId>
<DataTypeName>AddStockOrderAckData</DataTypeName> <Senders>
<ProcessRef>StockServer</ProcessRef> </Senders> <Receivers>
<ProcessRef>StockClient</ProcessRef> </Receivers> </Message> <Message>
<Name>ADD_STOCK_ORDER_NACK</Name> <MsgId>3</MsgId>
<DataTypeName>AddStockOrderNackData</DataTypeName> <Senders>
<ProcessRef>StockServer</ProcessRef> </Senders> <Receivers>
<ProcessRef>StockClient</ProcessRef> </Receivers> </Message> <Message>
<Name>CANCEL_STOCK_ORDER</Name> <MsgId>4</MsgId>
<DataTypeName>CancelStockOrderData</DataTypeName> <Senders>
<ProcessRef>StockClient</ProcessRef> </Senders> <Receivers>
<ProcessRef>StockServer</ProcessRef> </Receivers> </Message> <Message>
<Name>CANCEL_STOCK_ORDER_ACK</Name> <MsgId>5</MsgId>
<DataTypeName>CancelStockOrderAckData</DataTypeName> <Senders>
<ProcessRef>StockServer</ProcessRef> </Senders> <Receivers>
<ProcessRef>StockClient</ProcessRef> </Receivers> </Message> <Message>
<Name>CANCEL_STOCK_ORDER_NACK</Name> <MsgId>6</MsgId>
<DataTypeName>CancelStockOrderNackData</DataTypeName> <Senders>
<ProcessRef>StockServer</ProcessRef> </Senders> <Receivers>
<ProcessRef>StockClient</ProcessRef> </Receivers> </Message> <Message>
<Name>TEST</Name> <MsgId>7</MsgId>
<DataTypeName>TestData</DataTypeName> <Senders>
<ProcessRef>StockServer</ProcessRef> </Senders> <Receivers>
<ProcessRef>StockClient</ProcessRef> </Receivers> </Message> </Messages> <Processes> <Process>
<Name>StockClient</Name> </Process>
<Process>
<Name>StockServer</Name> </Process> </Processes>
</MessageRepository>
Этот репозиторий описывает сообщения, которыми обмениваются клиент (он называется StockClient) и сервер (StockServer) для выполнения различных операций. Читатели, знакомые с языком WSDL, отметят несомненное сходство; однако WSDL применяется для описания Web-сервисов и используется в контексте протокола SOAP, хотя технически его спецификация ни от какого протокола не зависит (http://www.w3.org/TR/wsdl).
Последние два примера в этой главе не связаны с задачей обмена сообщениями. Первый из них посвящен генерации кода на языке C++ из модели на унифицированном языке моделирования (UML), которая экспортируется инструментом разработки в виде файла на языке XMI (XML Metadata Interchange – обмен XML-метаданными). Во втором обсуждается применение XSLT для генерации XSLT.
Прежде чем переходить к самим примерам, хочу принести извинения за то, что в большинстве примеров используется язык C++. Я выбрал этот язык только потому, что хорошо с ним знаком; именно для него я написал реальные генераторы. Концептуально же весь каркас переносится и на другие языки, пусть даже фактический XSLT-код придется переделать1.
XSLT 2.0
В силу особенностей рецептов ниже я не стал приводить решения на XSLT 1.0 и 2.0 по отдельности, как в большинстве других глав. Читателям, которым интересно, как применять для генерации кода XSLT 2.0, рекомендую обратиться в главе 6. Многие описанные там особенности XSLT 2.0 и способы их применения, подходят и для генерации кода. Вот несколько общих рекомендаций:
? Применяйте новый атрибут separator элемента xsl:value-of.
При генерации кода часто приходится порождать последовательности элементов, разделенных какими-то символами (например, списки значений, разделенных запятыми). В таких случаях способность xsl:value-of автоматически вставлять разделитель может упростить генератор.
? Пользуйтесь возможностями последовательностей в XPath 2.0.
Один из самых серьезных недостатков генераторов, написанных на XSLT 1.0, – это их громоздкость. В генераторах часто встречаются циклы и условные конструкции, а команды xsl:for-each и xsl:choose трудно назвать образцами краткости. Однако многие задачи генерации можно решить на XPath 2.0 вместо XSLT. Нередко XSLT и XPath применяются для преобразования входного XML в одну или несколько последовательностей, после чего с помощью XPath они отображаются на код:
<!— Функция для генерации объявления функции на языке C. –>
<!– Используется преимущественно XPath 2.0 –>
<xsl:function name="ckbk:function-decl" as="xs:string*"> <xsl:param name="name" as="xs:string"/> <xsl:param name="returnType" as="xs:string"/> <xsl:param name="argNames" as="xs:string*"/> <xsl:param name="argTypesPre" as="xs:string*"/> <xsl:param name="argTypesPost" as="xs:string*"/> <xsl:variable name="c" select="count($argNames)"/> <xsl:sequence select="$returnType,
$name, ‘(‘,
for $i in 1 to $c return ($argTypesPre[$i], $argNames[$i], $argTypesPost[$i], if ($i ne $c) then ‘,’ else ”), ‘);’ "/>
</xsl:function>
? Модульный, повторно используемый XSLT-код проще писать в версии 2.0. Если вы собрались писать генераторы, позволяющие транслировать XML- описание на различные языки, то стоит получше освоить приемы, описанные в рецептах 6.3 и 6.4.
? В версии 2.0 стандартизован вывод нескольких документов.
Авторы генераторов для языков типа C и C++, где заголовочные и исходные файлы разделены, наверняка оценят удобство стандартизованной команды xsl:result-document для написания таблиц стилей, порождающих несколько выходных документов.
Мангано Сэл XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.
