Создание документов в формате PDF при помощи XStream и XSL-FO

Использование технологий XStream и XSL-FO для динамической генерации документов

В этой статье рассматривается применение XML-сериализации и XSL-FO для динамической генерации документов в формате PDF на основе бизнес-объектов Java™. Вы увидите, как страницы стилей XSL-FO позволяют отделить уровень представления данных от основного кода на Java, что дает возможность легко вносить изменения в формат PDF, не затрагивая другие компоненты приложения.

Брайан Дж. Стюарт, главный консультант, Aqua Data Technologies, Inc.

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



07.12.2011

Во многих бизнес-приложениях встречается задача создания документов PDF на основе данных, содержащихся в Java-объектах модели. Такие документы лучше всего рассматривать в качестве альтернативного представления бизнес-данных. Подобные представления должны быть легко модифицируемыми и слабо связанными с уровнем модели. В данной статье демонстрируется решение этой проблемы при помощи таких технологий, как XML, XStream и форматирующие объекты XSL (XSL-FO). Вначале мы кратко рассмотрим каждую из этих технологий, а затем интегрируем их в единое демонстрационное приложение.

Часто встречающиеся аббревиатуры

  • API: программный интерфейс приложений
  • CD: компакт-диск
  • CSS: каскадные таблицы стилей
  • HTML: язык разметки гипертекста
  • PDF: переносимый формат документов
  • XML: расширяемый язык разметки
  • XSL: расширяемый язык таблиц стилей
  • XSLT: расширяемый язык стилевых преобразований
  • W3C: консорциум World Wide Web

Итак, мы начнем с введения в используемые технологии.

Сериализация данных при помощи 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 дает целый ряд преимуществ.

Альтернативы XSL-FO

Существует ряд альтернативных технических Java-решений для генерации PDF. Наиболее популярным из них является библиотека iText, предоставляющая богатый набор функций для создания документов PDF и работы с ними.

  • Разделение ответственности. Слой представления данных (форматирование и разметка) отделяется от слоя модели (бизнес-объектов 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.

XSL-FO transformation process for a XML document (generate an FO document and finally a PDF document)

Архитектура предлагаемого решения

Основная цель предлагаемого решения заключается в реализации гибкой схемы создания документов PDF на основе произвольных объектов Java. При этом компонент, отвечающий непосредственно за генерацию PDF, должен быть максимально изолирован от Java-классов модели. В данном решении роль такого "изолятора" играет промежуточное представление объектов в XML, генерируемое при помощи XStream и далее преобразуемое в формат FO.

На рисунке 2 показано, как из описанных выше основных компонентов (бизнес-объекты Java, XStream) строится законченное решение для генерации документов в формате PDF, представляющих заказы на покупку.

Solution architecture to generate the XML document and then the PDF document

Вначале необходимо создать XML-представление данных путем сериализации бизнес-объекта типа IPurchaseOrder (заказ на покупку), который содержит экземпляры Java-классов IOrderItem и IAddress. Для максимальной гибкости в данном решении используются аннотации Java, которые отображают классы на соответствующие элементы и атрибуты документа XML, представляющего заказ на покупку.

На следующем этапе заказ на покупку, представленный в виде XML-файла, преобразуется в документ FO, который затем должен поступить на вход процессора FO. Данное преобразование выполняется при помощи страницы стилей XSLT. Полученный документ FO передается на обработку библиотеке Apache FOP – открытой реализации процессора FO, которая генерирует документ PDF.

Итоговый документ PDF должен выглядеть, как показано на рисунке 3. Для этого необходимо создать соответствующую страницу стилей XSL-FO.

Screenshot of sample Purchase Order PDF document

Диаграммы классов

Описываемое решение включает пять интерфейсов и реализующих их классов. Краткое описание каждого из интерфейсов приведено в таблице 1.

Таблица 1. Интерфейсы и их реализации
Имя интерфейса/классаОписание
IXmlSerializableИнтерфейс для классов, сериализуемых в XML.
IAddressИнтерфейс бизнес-объекта, представляющего адрес.
IPurchaseOrderИнтерфейс бизнес-объекта, представляющего заказ на покупку.
IOrderItemИнтерфейс бизнес-объекта, представляющего позицию заказа.
IPdfGeneratorИнтерфейс генератора, преобразующего любой бизнес-объект, который реализует интерфейс IXmlSerializable, в формат PDF.

На рисунке 4 показана диаграмма этих интерфейсов на универсальном языке моделирования (Unified Modeling Language - UML).

UML diagram of interfaces in Table 1

Наше решение состоит из реализаций показанных выше интерфейсов и класса Tester. Диаграмма этих бизнес-классов показана на рисунке 5.

UML diagram of the business objects

Кликните, чтобы увидеть увеличенное изображение

Схема 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 имеет следующие два основных раздела.

  • Определение макета страницы. В этом разделе описывается макет страницы, в том числе поля и содержимое (например, верхний и нижний колонтитулы, основной текст).
  • Последовательность страниц (содержимое и данные). В этом разделе находится содержимое страниц документа, включая верхние и нижние колонтитулы и основную часть страницы. Для каждой последовательности страниц должен быть определен макет.

Вначале рассмотрим модель макета. Основные компоненты страниц документа FO, такие как Region-Before, Region-After, Region-Body, Region-Start и Region-End, показаны на рисунке 6.

FO page layout model

Макет основной области страницы находится в элементе 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

Альтернативы для Apache FOP

На сегодняшний день существуют несколько коммерческих процессоров FO. Наиболее популярными из них являются RenderX XEP, Antenna House XSL Formatter и PTC Arbortext Publishing Engine. Ссылки приведены в разделе Ресурсы.

Далее остается последний шаг — создание документа 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.zip20 KБ

Ресурсы

Научиться

Получить продукты и технологии

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML
ArticleID=779648
ArticleTitle=Создание документов в формате PDF при помощи XStream и XSL-FO
publish-date=12072011