Во многих бизнес-приложениях встречается задача создания документов PDF на основе данных, содержащихся в Java-объектах модели. Такие документы лучше всего рассматривать в качестве альтернативного представления бизнес-данных. Подобные представления должны быть легко модифицируемыми и слабо связанными с уровнем модели. В данной статье демонстрируется решение этой проблемы при помощи таких технологий, как XML, XStream и форматирующие объекты XSL (XSL-FO). Вначале мы кратко рассмотрим каждую из этих технологий, а затем интегрируем их в единое демонстрационное приложение.
Итак, мы начнем с введения в используемые технологии.
Сериализация данных при помощи XStream
XStream – это простая, но мощная библиотека, позволяющая сериализовать объекты из XML и десериализовать их обратно в XML. Она отличается гибкостью, простотой, производительностью, экономной работой с памятью, низкими накладными расходами, а также хорошим управлением генерируемыми XML-документами. Еще одна ее привлекательная черта – поддержка графов объектов с высокой степенью вложенности, таких как каталог объектов, представляющих CD-диски, каждый из которых содержит ряд объектов-записей, имеющих, в свою очередь, свои атрибуты. Технология XStream не требует корректировки существующих бизнес-классов, если у вас нет желания использовать поддерживаемые ею Java-аннотации.
Преобразование данных при помощи XSLT
Следующим компонентом предлагаемого решения является XSLT – язык преобразований структурированных документов XML в различные форматы, в том числе XML и HTML. XSLT – это мощный и сложный язык, основанный на XML, включающий в себя ряд стандартных функций, в частности, функции для обработки строк и форматирования. Для запрашивания и выборки наборов вершин XML в XSLT активно используется XPath.
Преобразования документов XML, как правило, описываются в виде набора правил в страницах стилей XSLT. Страницы стилей состоят из шаблонов, которые определяют, как именно следует преобразовывать определенные элементы XML. Шаблоны можно применять к элементам с определенным именем, находящимся в определенном контексте (например, контекст /Book/Title отличается от /Catalog/Title) или с определенными атрибутами (например, /Book/@Title).
Форматирование документов при помощи XSL-FO
XSL-FO – это просто схема XML для представления данных. Она похожа на HTML и CSS, однако информация о стилях не вынесена в отдельный файл CSS, а хранится непосредственно в документах FO. Применение сериализации XML в сочетании с XSL-FO дает целый ряд преимуществ.
- Разделение ответственности. Слой представления данных (форматирование и разметка) отделяется от слоя модели (бизнес-объектов Java) благодаря сериализации XML и XSL-FO.
- Слабая связь между данными и представлением. Таблицы стилей XSL-FO, используемые для форматирования и определения макетов документов, не привязаны к конкретным бизнес-объектам: они обрабатывают исключительно их XML-представление. Подобное разделение между данными и представлением позволяет легко изменять формат документов PDF без внесения каких-либо изменений в объекты Java.
-
Гибкость представления. Таблицы стилей XSL-FO отличаются исключительной гибкостью в том, что касается управления форматированием и макетом. Подобно CSS, стандарт XSL-FO включает ряд форматирующих элементов для управления такими параметрами, как тип шрифта, размер шрифта, отступы и оформление текста. Кроме того, XSL-FO предоставляет следующие возможности:
- наборы атрибутов XSLT, которые позволяют многократно использовать одни и те же инструкции форматирования внутри документа;
- поддержка сложного разбиения на страницы, позволяющая выбирать нужную компоновку при выполнении ряда условий (например, можно использовать разные компоновки для первой, последней и остальных страниц);
- возможность добавления сносок к документу;
- определение колонтитулов страниц (они тоже могут различаться для первой, последней и остальных страниц);
- оптимизация верстки, в том числе поддержка инструкций "keep with next" (не отрывать абзац от следующего) и минимизация пробелов;
- наследование стилей (наследование параметров форматирования и стиля);
- оглавление и закладки (XSL-FO позволяет генерировать оглавление для всего документа PDF);
- поддержка таблиц, содержащих заголовочную, нижнюю и основную секции;
- поддержка различных направлений записи текста, в том числе слева направо, справа налево, сверху вниз и снизу вверх.
Работа с XSL-FO заключается в применении XSLT для преобразования документа XML в документ FO, который затем обрабатывается процессором FO для генерации итогового представления (в данном случае документа PDF). Весь процесс преобразования при помощи XSL-FO показан на рисунке 1.
Рисунок 1. Процесс преобразования с использованием XSL-FO
Архитектура предлагаемого решения
Основная цель предлагаемого решения заключается в реализации гибкой схемы создания документов PDF на основе произвольных объектов Java. При этом компонент, отвечающий непосредственно за генерацию PDF, должен быть максимально изолирован от Java-классов модели. В данном решении роль такого "изолятора" играет промежуточное представление объектов в XML, генерируемое при помощи XStream и далее преобразуемое в формат FO.
На рисунке 2 показано, как из описанных выше основных компонентов (бизнес-объекты Java, XStream) строится законченное решение для генерации документов в формате PDF, представляющих заказы на покупку.
Рисунок 2. Архитектура системы
Вначале необходимо создать XML-представление данных путем сериализации бизнес-объекта типа IPurchaseOrder (заказ на покупку), который содержит экземпляры Java-классов IOrderItem и IAddress. Для максимальной гибкости в данном решении используются аннотации Java, которые отображают классы на соответствующие элементы и атрибуты документа XML, представляющего заказ на покупку.
На следующем этапе заказ на покупку, представленный в виде XML-файла, преобразуется в документ FO, который затем должен поступить на вход процессора FO. Данное преобразование выполняется при помощи страницы стилей XSLT. Полученный документ FO передается на обработку библиотеке Apache FOP – открытой реализации процессора FO, которая генерирует документ PDF.
Итоговый документ PDF должен выглядеть, как показано на рисунке 3. Для этого необходимо создать соответствующую страницу стилей XSL-FO.
Рисунок 3. Представление заказа на покупку в формате PDF
Описываемое решение включает пять интерфейсов и реализующих их классов. Краткое описание каждого из интерфейсов приведено в таблице 1.
Таблица 1. Интерфейсы и их реализации
| Имя интерфейса/класса | Описание |
|---|---|
IXmlSerializable
| Интерфейс для классов, сериализуемых в XML. |
IAddress
| Интерфейс бизнес-объекта, представляющего адрес. |
IPurchaseOrder
| Интерфейс бизнес-объекта, представляющего заказ на покупку. |
IOrderItem
| Интерфейс бизнес-объекта, представляющего позицию заказа. |
IPdfGenerator
| Интерфейс генератора, преобразующего любой бизнес-объект, который реализует интерфейс IXmlSerializable, в формат PDF. |
На рисунке 4 показана диаграмма этих интерфейсов на универсальном языке моделирования (Unified Modeling Language - UML).
Рисунок 4. UML-диаграмма интерфейсов, представленных в таблице 1
Наше решение состоит из реализаций показанных выше интерфейсов и класса Tester. Диаграмма этих бизнес-классов показана на рисунке 5.
Рисунок 5. UML-диаграмма бизнес-классов
Схема XML для представления заказов на покупку
В таблице 2 описаны ключевые элементы XML-схемы для представления заказов на покупку.
Таблица 2. Компоненты схемы представления заказов на покупку
| Ключевой элемент | Описание |
|---|---|
OrderDate
| Содержит дату заказа на покупку. |
CompanyAddress
| Содержит адресные данные компании, в том числе ее название, улицу, город, штат и почтовый индекс. |
CustomerAddress
| Содержит адресные данные покупателя, в том числе название компании, улицу, город, штат и почтовый индекс. |
Items
| Содержит список заказанных товаров, для каждого из которых указывается идентификатор, название и количество заказанных единиц. |
Генерация XML-представления заказа на покупку при помощи XStream
Первый этап генерации XML-документа, представляющего заказ на покупку, заключается в сериализации бизнес-объекта типа PurchaseOrder в XML.
В данном случае управление генерацией XML через XStream осуществляется при помощи аннотаций. Аннотации являются предпочтительным способом конфигурирования формата XML, генерируемого XStream, поскольку они, во-первых, просты в использовании, а во-вторых, позволяют описывать отображение в XML на уровне классов, тем самым повышая надежность кода сериализации. Без аннотаций любые изменения бизнес-классов автоматически приводили бы к необходимости корректировки кода сериализации, в то время как использование аннотаций делает код чище, так как описание сериализации не отделяется от определения классов и полей. Таким образом, при добавлении нового поля в класс вы можете сразу в том же файле определить, как это поле должно сериализоваться в XML.
Замечание. Использование аннотаций возможно только в Java версии 1.5 и выше. В данной статье они применяются для управления генерируемым форматом XML.
Обратите внимание на класс PurchaseOrder в листинге 1, который реализует интерфейсы IPurchaseOrder и IXmlSerializable . Ключевую роль при сериализации в XML играет метод toXml, определенный в интерфейсе IXmlSerializable. Данная реализация содержит минимально необходимый объем кода, вызывающий сериализатор XStream с включенной поддержкой аннотаций. Для каждого внутреннего поля XStream ищет аннотации, описывающие способ сериализации соответствующего класса. Если таковые не найдены, то используются соглашения сериализации по умолчанию.
Листинг 1. Класс PurchaseOrder
class@XStreamAlias("PurchaseOrder")
public class PurchaseOrder implements IPurchaseOrder, IXmlSerializable {
// закрытые переменные-члены
/** номер заказа. */
@XStreamAlias("OrderId")
@XStreamAsAttribute
private String m_orderId = null;
/** дата заказа. */
@XStreamAlias("OrderDate")
private Date m_orderDate = null;
/** адрес компании. */
@XStreamAlias("CompanyAddress")
private IAddress m_companyAddr = null;
/** адрес клиента. */
@XStreamAlias("CustomerAddress")
private IAddress m_customerAdress = null;
/** товары. */
@XStreamAlias("Items")
private ArrayList<IOrderItem> m_items = null;
...
// Метод интерфейса IXmlSerializable
public String toXml() {
XStream xstream = new XStream();
xstream.autodetectAnnotations(true);
return xstream.toXML(this);
}
}
|
Аннотации @XStreamAlias указывают XStream, какие элементы и атрибуты XML следует использовать в процессе сериализации и десериализации. В свою очередь аннотация XStreamAsAttribute означает, что данное поле должно сериализоваться в виде атрибута, а не отдельного элемента XML.
Далее следует создать классы Address и OrderItem (их исходный код содержится в архиве к статье). Им необязательно реализовывать интерфейс IXmlSerializable, поскольку они не будут сериализоваться отдельно от класса PurchaseOrder. Реализация этих классов достаточно очевидна и состоит в основном из частных переменных экземпляра и открытых get/set-методов. При этом частные переменные экземпляра снабжены аннотациями для управления сериализацией через XStream.
Вызов метода toXml приводит к генерации документа XML, аналогичного показанному в листинге 2.
Листинг 2. Представление заказа на покупку в XML
<PurchaseOrder OrderId="PO-123-456789">
<OrderDate>2009-06-14 13:05:02.251 EDT</OrderDate>
<CompanyAddress>
<CompanyName>ACME Company</CompanyName>
<StreetAddress>123 Main Street</StreetAddress>
<City>Orlando</City>
<State>FL</State>
<ZipCode>32801</ZipCode>
</CompanyAddress>
<CustomerAddress>
<CompanyName>A++</CompanyName>
<StreetAddress>123 8th Avenue</StreetAddress>
<City>Orlando</City>
<State>FL</State>
<ZipCode>32801</ZipCode>
</CustomerAddress>
<Items>
<Item ItemId="A1B2C3" ItemName="Widget" Quantity="100" ItemCost="100.5"/>
<Item ItemId="C3B2A1" ItemName="Micro-Widget" Quantity="1000" ItemCost="10.75"/>
</Items>
</PurchaseOrder>
|
Генерация представления заказа на покупку в виде документа FO
На следующем шаге выполняется преобразование заказа на покупку из XML (см. листинг 2) в документ FO. При написании страниц XSLT часто бывает полезно создать прототип того, как должен выглядеть результат преобразования. Этот подход работает особенно удачно в случае использования XSL-FO, поскольку он позволяет проверить форматирование и макет путем тестовой обработки прототипа документа FO процессором Apache FOP. После проверки корректности макета вам останется только создать страницу XSL-FO для преобразования исходных документов XML в документы FO.
Любой документ FO имеет следующие два основных раздела.
- Определение макета страницы. В этом разделе описывается макет страницы, в том числе поля и содержимое (например, верхний и нижний колонтитулы, основной текст).
- Последовательность страниц (содержимое и данные). В этом разделе находится содержимое страниц документа, включая верхние и нижние колонтитулы и основную часть страницы. Для каждой последовательности страниц должен быть определен макет.
Вначале рассмотрим модель макета. Основные компоненты страниц документа FO, такие как Region-Before, Region-After, Region-Body, Region-Start и Region-End, показаны на рисунке 6.
Рисунок 6. Модель макета страницы документа FO
Макет основной области страницы находится в элементе Region-Body (см. рисунок 6). Элементы Region-Before и Region-After, как правило, используются для верхнего и нижнего колонтитулов. Кроме того, часть содержимого документа может размещаться в правой и левой областях (элементы Region-Start и Region-End).
Эти области могут включать следующие типы содержимого.
- Содержимое со статическим размещением, которое может находиться только в статических областях, например в верхнем или нижнем колонтитуле.
- Содержимое с динамическим размещением, привязанное к макету страницы, которое может размещаться на нескольких страницах. К этому типу относится содержимое основной области документа FO.
Макет страницы включает в себя определение размера страницы и отступов. При этом в пределах одного документа может быть несколько макетов страниц. В листинге 3 показано описание простого макета страницы размером 8.5 x 11 дюймов.
Листинг 3. Пример макета страницы
<fo:layout-master-set>
<fo:simple-page-master
margin-right="1in"
margin-left="1in"
margin-bottom="0.5in"
margin-top="1in"
page-width="8.5in"
page-height="11in"
master-name="standardletter">
<fo:region-body
margin-bottom="1in"
margin-top="1in"/>
<fo:region-after extent="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>
|
Стандарт FO включает следующие элементы контента, которые могут быть добавлены к элементам со статическим или динамическим содержимым.
-
block - контейнер, аналогичный тегу
<DIV>в HTML. Каждый элемент block может содержать текст или другие элементы FO. -
inline - контейнер, аналогичный HTML-тегу
<SPAN>. -
table - таблица, аналогичная тегу
<TABLE>в HTML. -
list - список, похожий на HTML-теги
<UL>и<OL>. -
external-graphic - изображение или другой графический объект; аналогичен тегу
<IMG>в HTML. - character - единичный символ; как правило, используется в случае необходимости специального форматирования отдельных символов.
-
basic-link - ссылка в документе, аналогичная тегу
<A>в HTML.
Создание страницы XSLT для преобразования заказа на покупку
Преобразование документа XML, представляющего заказ на покупку, начинается с корневого узла PurchaseOrder. К нему применяется шаблон, показанный в листинге 4, который генерирует элементы layout-master-set и page-sequence.
Листинг 4. Шаблон PurchaseOrder
<xsl:template match="PurchaseOrder">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<!-- Определение макета страницы, в том числе полей -->
<fo:layout-master-set>
<!-- Размер страницы и полей -->
<fo:simple-page-master
master-name="all"
page-height="11in"
page-width="8.5in"
margin-top="0.25in"
margin-bottom="0.25in"
margin-left="1in"
margin-right="1in">
<!—Макет основной области -->
<fo:region-body margin-top="2in"
margin-bottom="1in" />
<!—Макет верхнего колонтитула -->
<fo:region-before extent="2in" />
<!—Макет нижнего колонтитула -->
<fo:region-after extent="1in" />
</fo:simple-page-master>
</fo:layout-master-set>
<!-- Создание последовательности страниц-->
<fo:page-sequence master-reference="all">
<!-- Создание верхнего колонтитула -->
<fo:static-content flow-name="xsl-region-before">
<!-- Вывод адреса компании в верхнем колонтитуле -->
<xsl:apply-templates
select="CompanyAddress" />
<fo:block font-size="18pt"
font-family="sans-serif"
line-height="1.5em"
background-color="black"
color="white"
text-align="center"
>PURCHASE ORDER</fo:block>
</fo:static-content>
<!-- Создание нижнего колонтитула -->
<fo:static-content flow-name="xsl-region-after">
<!-- Добавление номера страницы к нижнему колонтитулу -->
<fo:block text-align="end"
font-size="10pt"
font-family="serif">
Page <fo:page-number />
</fo:block>
</fo:static-content>
<!-- Вывод основного содержимого документа -->
<fo:flow flow-name="xsl-region-body">
<!-- Вывод информации о заказе (дата, номер)-->
<xsl:call-template name=
"DisplayOrderInformation" />
<!-- Вывод адреса покупателя -->
<xsl:apply-templates select=
"CustomerAddress" />
<!-- Вывод товаров-->
<xsl:apply-templates select="Items" />
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
|
На следующем шаге генерируется верхний колонтитул страницы, содержащий логотип и адрес компании. При формировании статического содержимого секции Region-Before вызывается отдельный шаблон, применяющийся ко всем элементам CompanyAddress. Он создает таблицу из двух колонок, в первую из которых помещается логотип компании, а во вторую – ее адрес. Шаблон преобразования элементов CompanyAddress приведен в листинге 5.
Листинг 5. Шаблон CompanyAddress
<xsl:template match="CompanyAddress">
<fo:table width="100%">
<fo:table-column column-width="40%" />
<fo:table-column column-width="60%" />
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block>
<fo:external-graphic
src="url(D:\workspace\DwArticle5\resources\CompanyLogo.jpg)" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<fo:block text-align="right">
<xsl:value-of select="./StreetAddress" />
</fo:block>
<fo:block text-align="right">
<xsl:value-of select="concat(./City, ' ',
./State, ' ',
./ZipCode)" />
</fo:block>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</xsl:template>
|
Далее в документе выводится информация о заказе, в частности, его идентификатор и дата. Для этой цели служит шаблон с именем DisplayOrderInformation, вызываемый в следующей строке:
<xsl:call-template name="DisplayOrderInformation" /> |
Предыдущие шаблоны были безымянными и применялись инструкцией <xsl:apply-templates/>. Именованные шаблоны полезны для реализации процедур, возвращающих значения, либо в тех случаях, когда необходимо обрабатывать не все дочерние элементы, а специально выбранный набор вершин. Кроме того, их можно с успехом применять в рекурсивных сценариях обработки. В нашем случае такой шаблон используется для вывода информации о заказе исключительно в учебных целях; существуют и другие, более оптимальные варианты. Текст нашего именованного шаблона приведен в листинге 6.
Листинг 6. Шаблон DisplayOrderInformation
<xsl:template name="DisplayOrderInformation">
<fo:block text-align="right">
<fo:inline font-weight="bold">Id:</fo:inline>
<xsl:value-of select="./@OrderId" />
</fo:block>
<fo:block text-align="right">
<fo:inline font-weight="bold">Order Date:</fo:inline>
<xsl:value-of select="./OrderDate" />
</fo:block>
</xsl:template>
|
Данный шаблон преобразует элементы Items и Item в таблицу, каждая строка которой соответствует элементу Item. Кроме того, строки таблицы нумеруются, для каждого товара подсчитывается его общая стоимость, а внизу добавляется строка с итоговой суммой всего заказа. Для нумерации используется инструкция <xsl:number/>, генерирующая последовательность чисел. Общая стоимость одного товара вычисляется путем умножения числа заказанных единиц товара на стоимость единицы. В данном примере показаны два рекурсивных способа вычисления итоговой суммы всего заказа: при помощи рекурсивного именованного шаблона и с использованием режима применения шаблона и рекурсии. В обоих случаях итоговая сумма заказа вычисляется путем суммирования общей стоимости по всем заказанным товарам.
В листинге 7 показаны все шаблоны, отвечающие за генерацию таблицы, которая содержит все товары, перечисленные в заказе.
Листинг 7. Шаблоны Items в Item
<!-- Шаблон для элемента Items-->
<!-- Вывод таблицы -->
<xsl:template match="Items">
<fo:block font-weight="bold" background-color="black" color="white"
padding="2pt">ITEMS</fo:block>
<fo:table width="100%">
<!-- Указание ширины колонок таблицы -->
<fo:table-column column-width="10%" />
<fo:table-column column-width="15%" />
<fo:table-column column-width="30%" />
<fo:table-column column-width="15%" />
<fo:table-column column-width="15%" />
<fo:table-column column-width="15%" />
<!-- Заголовочная строка таблицы -->
<fo:table-header>
<fo:table-row>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>#</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Item ID</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Description</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Quantity</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Item Cost</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold"
>Total Cost</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:apply-templates />
<!-- Итоговая строка таблицы, охватывающая все колонки.
Общая сумма выводится жирным шрифтом.-->
<fo:table-row>
<fo:table-cell border="solid black 1px"
number-columns-spanned="5">
<fo:block font-weight="bold"
text-align="right">Total
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block font-weight="bold">
<!-- Значение данной переменной выводится ниже -->
<xsl:variable name="total">
<!-- Следующую строку следует раскомментировать
при использовании режимов применения и рекурсии. -->
<!--
<xsl:apply-templates select="Item[1]"
mode="calculateTotal" />-->
<!-- Инструкцию call-template следует закомментировать, если
не используется вызов именованного шаблона. -->
<xsl:call-template name="calculateTotal">
<xsl:with-param name="nodes" select="Item" />
</xsl:call-template>
</xsl:variable>
<!-- Вывод значения переменной "total" -->
<xsl:value-of select="format-number($total, '$#,##0.00')"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</xsl:template>
<!-- Шаблон для элементов Item-->
<!-- Выводит тег TR для каждого элемента Item -->
<xsl:template match="Item">
<fo:table-row>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:number/>
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:value-of select="@ItemId" />
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:value-of select="@ItemName" />
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:value-of select="@Quantity" />
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<fo:block>
<xsl:value-of select="format-number(@ItemCost, '$#,##0.00')" />
</fo:block>
</fo:table-cell>
<fo:table-cell border="solid black 1px">
<xsl:variable name="total" select="@Quantity * @ItemCost" />
<fo:block>
<xsl:value-of select="format-number($total, '$#,##0.00')" />
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
|
Как уже говорилось, в данном примере представлены два основных способа подсчета общей суммы заказа. Первый из них, основанный на использовании именованного шаблона, приведен в листинге 8. Шаблон calculateTotal вызывается рекурсивно для всех элементов Item. Принцип работы шаблона поясняется во вложенных комментариях.
Листинг 8. Именованный шаблон для вычисления общей суммы заказа
<xsl:template name="calculateTotal">
<xsl:param name="nodes" select="/.." />
<xsl:param name="subtotal" select="0" />
<xsl:variable name="total"
select="$subtotal + ($nodes[1]/@ItemCost * $nodes[1]/@Quantity)" />
<xsl:choose>
<!-- Проверка наличия необработанных элементов Item.
Если таких нет, обработка прекращается, и возвращается результат. -->
<xsl:when test="not($nodes[2])">
<xsl:value-of select="$total" />
</xsl:when>
<!-- Если необработанные вершины Item еще остались,
рекурсивно вызывается этот же шаблон. -->
<xsl:otherwise>
<xsl:call-template name="calculateTotal">
<xsl:with-param name="nodes"
select="$nodes[position() > 1]" />
<xsl:with-param name="subtotal" select="$total" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
|
Второй вариант заключается в рекурсивных вызовах шаблонов с разными режимами применения. Шаблон, показанный в листинге 9, применяется ко всем элементам Item в случае, если режим равен calculateTotal. При этом необходимо создать пустой шаблон для тех дочерних вершин, которые должны игнорироваться при применении шаблона в этом режиме. Как и его именованный аналог, данный шаблон применяется рекурсивно до тех пор, пока существует хотя бы один необработанный элемент Item. Принцип его работы также описывается в комментариях.
Листинг 9. Шаблон для элементов Item, применяемый в режиме calculateTotal
<xsl:template match="Item" mode="calculateTotal">
<xsl:param name="subtotal" select="0" />
<xsl:variable name="total"
select="$subtotal + (@ItemCost * @Quantity)" />
<xsl:choose>
<!-- Проверка наличия необработанных элементов Item.
Если таких нет, обработка прекращается, и возвращается результат. -->
<xsl:when test="not(following-sibling::Item)">
<xsl:value-of select="$total" />
</xsl:when>
<xsl:otherwise>
<!-- Если необработанные вершины Item еще остались,
рекурсивно применяется этот же шаблон. -->
<xsl:apply-templates select="following-sibling::Item[1]"
mode="calculateTotal">
<xsl:with-param name="subtotal" select="$total" />
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
|
Далее остается последний шаг — создание документа PDF на основе XML, сгенерированного XStream, при помощи только что созданной страницы XSL. Для этого служит класс PdfGenerator, который применяет преобразование XSL к документу XML, используя стандартный класс Transformer, и передает полученный документ FO на вход процессора Apache FOP.
Листинг 10. Класс PdfGenerator
public class PdfGenerator implements IPdfGenerator {
public OutputStream generate(IXmlSerializable object, String stylesheetPath,
OutputStream pdfContent) {
try {
// Указание источника для XML
String xml = object.toXml();
StreamSource xmlSource =
new StreamSource(new ByteArrayInputStream(xml.getBytes()));
// Указание источника для страницы стилей
File xslFile = new File(stylesheetPath);
FileInputStream xslFileStream = new FileInputStream(xslFile);
StreamSource xslSource = new StreamSource(xslFileStream);
// Получение XSL-процессора
TransformerFactory tfactory = TransformerFactory.newInstance();
Transformer transformer = tfactory.newTransformer(xslSource);
// Настройка процессора FOP
FopFactory fopFactory = FopFactory.newInstance();
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
foUserAgent.setProducer(this.getClass().getName());
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent,
pdfContent);
// Выполнение преобразования
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(xmlSource, res);
} catch (FileNotFoundException e) { e.printStackTrace();
} catch (TransformerException e) { e.printStackTrace();
} catch (FOPException e) { e.printStackTrace();
}
return pdfContent;
}
}
|
Рекомендации по использованию XStream и XSL-FO
При работе с XStream и XSL-FO следует придерживаться ряда рекомендаций.
- Используйте Java-аннотации для конфигурирования XStream, поскольку они повышают гибкость управления и способствуют слабой связанности уровней приложения.
- Используйте режимы применения шаблонов, а не именованные шаблоны, поскольку преобразование наборов вершин лучше укладывается в философию XSLT, чем вызов процедур.
- Не опасайтесь рекурсивного применения шаблонов. Попытки избежать рекурсии очень часто приводят к ненужному усложнению страниц XSL.
- Тщательно продумывайте схему XML. Непродуманные схемы, сделанные на скорую руку, часто бывают сложны в поддержке и дальнейшем развитии. В частности, следует внимательно подходить к вопросу выбора между атрибутами и элементами. Например, если вместо элемента
Publisher(издатель) использовать одноименный атрибут, то в дальнейшем будет сложно добавить дополнительную информацию об издателе в документ XML.
Прочитав эту статью, вы увидели, насколько легко можно генерировать документы PDF на основе бизнес-объектов Java при помощи XStream и XSL-FO. Принцип разделения ответственности позволяет отделить уровень представления данных от самих бизнес-объектов, в результате чего можно корректировать представление (т. е. документы PDF), не внося никаких изменений в Java-классы. Для этой цели служат страницы стилей XSL, в которых описываются все требования к представлению данных. Кроме того, такое решение позволяет использовать несколько страниц XSL для генерации представлений разного типа, например разных документов, на основе одних и тех же бизнес-объектов Java.
| Описание | Имя | Размер | Метод загрузки |
|---|---|---|---|
| Исходный Java-код примеров к статье | xstream-code.zip | 20 KБ | HTTP |
Научиться
- Оригинал статьи: Generate PDFs with XStream and XSL-FO (Брайан Дж. Стюарт, developerWorks, сентябрь 2009 г.). (EN)
- Прочитайте пошаговое руководство W3C по XSL-FO, в котором описывается формат XML для представления данных на экране, в печатных документах и на других носителях. (EN)
- Прочитайте спецификацию W3C для XSL 1.1, в которой описываются синтаксис и возможности XSL для определения стилевых преобразований. (EN)
- Принципы сериализации данных рассматриваются в статье Сериализация Java-классов в XML при помощи XStream (Раджив Бангалор, Rajiv Bangalore), developerWorks, июль 2009 г.). (EN)
Получить продукты и технологии
- Загрузите библиотеку XStream, предоставляющую API для сериализации Java-классов в XML и обратного преобразования. (EN)
- Загрузите библиотеку Apache FOP, служащую для генерации PDF на основе документов FO. (EN)
- Обратите внимание на три популярных процессора FO (EN):

Брайан Стюарт (Brian J. Stewart) работает главным консультантом в компании Aqua Data Technologies, которую он основал и которая занимается управлением информацией, XML-технологиями и корпоративными клиент/серверными и Web-системами. Он проектирует и разрабатывает корпоративные решения на базе платформ J2EE и .NET.