Xylophone

Материал из Course Orchestra
Перейти к: навигация, поиск

Общая информация

Система предназначена для формирования сложноструктурированных отчётов в формате электронных таблиц (XLS или XLSX), PDF-файлов или прямого вывода отчётов на принтер на основе данных, представленных в формате XML.

Система имеет два режима чтения исходных XML-файлов: DOM и SAX. Режим DOM допускает большую гибкость в определении структуры отчёта и чтении XML-данных за счёт увеличенных требований к памяти и ресурсам. Режим SAX вводит ряд несущественных ограничений на структуру XML-документа и дескриптора отчета, однако является на порядок более эффективным по скорости и потреблению памяти, а потому пригоден для формирования огромных отчётов.

Система не требует установки MS Excel, Adobe Acrobat и любого иного программного обеспечения на машину, формирующую отчёты. Система для своей работы использует следующие (устанавливаемые вместе с системой) библиотеки с открытым исходным кодом:

  • Apache POI — для разбора и создания Excel-файлов.
  • Apache FOP — для создания PDF-файлов и вывода на печать на принтер.

Общий принцип работы системы показан на диаграмме:

Xml2spreadsheet.png

На вход подаётся:

  • XML-файл с исходными данными.
  • Шаблон в формате .XLS или .XSLX, в котором заданы визуальная раскладка и форматирование будущего документа.
  • Написанный на языке XML дескриптор, описывающий способ обхода тэгов исходного XML-файла.

На выходе получается файл, соответствующий формату шаблона (.XLS, если шаблон имел формат .XLS, или .XLSX, если шаблон имел формат .XSLX). Далее, если в том есть необходимость, документ XLS может быть передан на вторую фазу обработки и превращён в .PDF-файл или отправлен напрямую на печать.

Установка

  • Дистрибутив доступен на sourceforge

Первый этап: формирование XLS и XLSX-файлов

Использование системы

Систему Xylophone можно использовать двумя способами:

  • из командной строки.
  • как Java-библиотеку, используемую в скрипте Flute или иным другим способом,

Использование из командной строки

Для использования из командной строки распакуйте файл xylophone-VERSION-bin.zip и выполните в папке, в которой находится xylophone.jar, следующую команду:


java -jar xml2spreadsheet.jar -data datafile.xml -template template.xls[x] -descr descriptor.xml -out result.xls [-sax] [-copytemplate]


где

  • datafile.xml — файл с XML-данными отчёта
  • template.xls[x] — шаблон в формате xls или xlsx
  • descriptor.xml — настроечный файл (он же дескриптор)
  • result.xls — имя результирующего файла. По расширению определяется его формат (XLS или XLSX)
  • sax — наличие данного параметра указывает на SAX-режим работы (описание см. ниже)
  • copytemplate — наличие данного параметра указывает на необходимость копировать шаблон полностью перед началом обработки.

Использование в качестве библиотеки

Для использования в качестве библиотеки необходимо проимпортировать класс ru.curs.xylophone.XML2Spreadsheet и запустить статический метод process данного класса: XML2Spreadsheet.process(...). Существует несколько перегруженных вариантов данного метода со следующими параметрами:

  • File/InputStream xmlData — исходные данные.
  • File/InputStream xmlDescriptor — дескриптор, описывающий порядок итерации по исходным данным.
  • File/InputStream template — шаблон отчёта.
  • ru.curs.flute.xml2spreadsheet.OutputType outputType — тип шаблона отчёта (OpenOffice, XLS, XLSX). Данный параметр не указывается, если для параметров xmlData, xmlDescriptor, template используется тип File. В этом случае тип определяется по расширению файла.
  • boolean useSAX — режим процессинга (DOM или SAX, описание различий см. ниже).
  • boolean copyTemplate — копировать ли шаблон полностью в результирующий файл перед началом обработки. Необязательный параметр, по умолчанию false. Данная опция нужна, если необходимо перенести из шаблона в результирующий файл диаграммы, графику и прочие объекты, не переносимые при отключенной данной опции.
  • OutputStream output — поток, в который записывается результирующий отчёт.

Имеется также метод XML2Spreadsheet.toPOIWorkbook(xmlData, xmlDescriptor, template, useSAX, copyTemplate), возвращающий объектное представление результирующего отчёта в виде экземпляра класса org.apache.poi.ss.usermodel.Workbook. Этот метод может быть полезен, если необходимо выполнить пост-обработку отчёта перед окончательным сохранением.

Для запуска из командной строки см. статью Запуск Xylophone из командной строки.

Управляемый обход XML

Вывод в документ-электронную таблицу осуществляется следующим образом:

  • система определённым образом обходит элементы (тэги) XML-файла с данными (алгоритм обхода управляется настроечным XML-файлом, или, как мы его ещё называем, файлом-дескриптором),
  • в заданные моменты осуществляется копирование фрагментов шаблона в документ результата, при этом поля данных шаблона заполняются информацией в контексте текущего элемента XML-файла с данными.

В процессе обхода XML-файла с данными система может находиться в одном из трёх режимов:

  1. Состояние чтения элемента (element)
  2. Состояние вывода (output)
  3. Состояние итерации (iteration)

Граф перехода режимов:

Xml2spreadsheet statechart.jpg

Далее даётся описание обхода XML-файла с данными через описание трёх возможных режимов.

Режим чтения элемента

В момент начала обработки система устанавливает в качестве текущего контекста корневой элемент документа с данными и в качестве режима — режим чтения элемента. В начале обработки система ожидает, что корневым элементом настроечного файла будет тэг вида <element name=”[имя_корневого_элемента]”>, т. е. значение атрибута name корневого тэга должно совпадать с именем корневого элемента в файле данных. В противном случае система не осуществит вывода. Т. е., если файл с данными имеет вид

<root>
    ...
</root>

то настроечный файл обязан иметь вид

<element name="root">
    ...
</element>

Атрибут name также обязаны иметь все прочие тэги <element>.

В режиме чтения элемента система читает субэлементы тэга <element> настроечного файла. Они могут быть двух типов: <output> и <iteration>. Соответственно, система переходит в режимы вывода или итерации. Субэлементов вида <output> и <iteration> в <element> может быть сколько угодно, и они могут идти в любой последовательности — система обрабатывает их последовательно один за другим.

Для атрибута name поддерживаются следующие варианты значений:

  1. Прямое указание имени тэга. В этом случае интерпретатор переходит к обработке <element> лишь в том случае, если имя тэга в сканируемом файле данных совпадает с указанным в атрибуте.
  2. Значение * («звёздочка»). В этом случае для обработки подходит любой тэг в файле данных.
  3. Простое XQuery-выражение типа tagname[@attribute='value']. Обработка происходит лишь в случае, когда имя тэга совпадает с tagname, а значение атрибута attribute равно value. ВНИМАНИЕ: поддерживаем именно только такое выражение, с единственным атрибутом и знаком "=". Знаки <, >, а также логические выражения с несколькими условиями работать НЕ БУДУТ. Только выражение по шаблону tagname[@attribute='value'] (кавычки могут быть одинарными или двойными, по обстоятельствам, допускается также &quot;).
  4. Значения (before) и (after). Служат для вывода «пролога» и «эпилога» последовательности элементов.

Режим итерации

В режиме итерации система работает следующим образом:

  • Запоминается значение контекста текущего элемента данных, чтобы восстановить его после завершения итерации.
  • Далее, в зависимости от значения атрибута index:
    • Если тэг <iteration> не имеет атрибута index, читаются все субэлементы текущего элемента документа с данными и последовательно каждый из них выставляется в качестве текущего.
    • Если тэг <iteration> имеет атрибут index, читается и выставляется в качестве текущего конкретный субэлемент текущего элемента. В качестве значения атрибута index может быть указано целое число, начиная с нуля.
  • После того, как прочитан и установлен очередной текущий элемент, система последовательно читает все субэлементы тэга <iteration>, которые могут быть только типа <element>.
  • Если встречается тэг <element> с атрибутом name=”(before)”, то прежде всего обрабатывается родительский элемент данных (это даёт возможность вывести “header” последовательности элементов).
  • Если получается так, что значение атрибута name тэга <element> совпадает с именем текущего элемента (или атрибут name установлен в значение '*'), то система переходит в режим чтения элемента, описанный выше.
  • Если встречается тэг <element> с атрибутом name=”(after)”, то после всего обрабатывается родительский элемент данных (это даёт возможность вывести “footer” последовательности элементов).
  • Тэг <iteration> может иметь атрибут mode, устанавливающий режим компоновки фрагментов шаблона в результирующем файле. Возможные значения:
    • отсутствие значения — фрагменты шаблона, выводимые в режиме output, компонуются в результирующем документе сверху вниз.
    • horizontal — фрагменты шаблона компонуются в результирующем документе слева направо.
  • Тэг <iteration> может иметь атрибут merge. Если целочисленное значение этого атрибута больше нуля и равно N, то N первых столбцов (или N первых строк, в зависимости от вертикального или горизонтального режима) блока, образованного итерацией, будут объединены в одну ячейку. Нужно для построения отчётов, в которых объединённая ячейка должна охватывать переменное количество строк или столбцов.
  • Тэг <iteration> может иметь атрибут regionName. Если значение этого атрибута задано, то блок, образованный итерацией, будет в конце итерации преобразован в именованный диапазон с указанным именем.
  • После завершения итерации система восстанавливает значение контекста текущего элемента, по субэлементам которого началась итерация.

Т. к. может быть сколько угодно тэгов <iteration> внутри тэга <element> и тэгов <element> внутри тэга <iteration>, это позволяет гибко организовывать сложные обходы файла с данными. Например, если файл с данными имеет структуру

<root>
    <a></a>
    <a></a>
    <b></b>
    <a></a>
    <a></a>
    <b></b>
    <b></b>
    <a></a>
    <b></b>
</root>

— т. е. внутри корневого элемента тэги <a> и <b> идут в произвольном порядке — то для того, чтобы обрабатывать тэги <a> и <b> в той последовательности, как они идут в файле данных, настроечный файл должен иметь вид:

<element name="root">
    <iteration>
        <element name="a"> 
        </element>
        <element name="b">
        </element>
    </iteration>    
</element>

или же вид

<element name="root">
    <iteration>
        <element name="*"> 
        </element>
    </iteration>    
</element>

а для того, чтобы обработать сначала все тэги <a>, а затем все тэги <b> — вид

<element name="root">
    <iteration>
        <element name="a"> 
        </element>
    </iteration>
    <iteration>
        <element name="b"> 
        </element>
    </iteration>
</element>

Для того, чтобы обработать нулевой, а затем первый тэг, вне зависимости от имени этих тэгов, настроечный файл должен иметь вид:

<element name="root">
    <iteration index="0">
        <element name="*"> 
        </element>
    </iteration>
    <iteration index="1">
        <element name="*"> 
        </element>
    </iteration>
</element>

Режим вывода

Оказавшись в режиме вывода, система копирует фрагмент шаблона в определённое место результирующего файла и заполняет этот фрагмент данными на основе текущего элемента файла данных. Тэги <output> могут встречаться только внутри тэга <element>, однако их может быть сколько угодно и они могут идти в произвольном порядке вперемешку с тэгами <iteration>. Атрибутами тэга <output> являются

  • sourcesheet — опциональный атрибут, указывающий на лист книги шаблона, с которого берётся диапазон вывода. Если не указывать, то применяется текущий (последний использованный) лист.
  • range – опциональный атрибут, диапазон шаблона, копируемый в результирующий документ, например “A1:M10”, или “5:6”, или “C:C”. Применение диапазонов строк типа “5:6” в режиме вывода left-to-right и диапазонов столбцов типа “C:C” в режиме вывода top-to-bottom приведёт к ошибке,
  • worksheet – опциональный атрибут. Если он определён, то в файле вывода создаётся новый лист и позиция вывода смещается в ячейку A1 этого листа. Если определено значение этого атрибута, равное константе или XPath-выражению, то имя листа подставляется из результата этой константы или выражения.
  • repeatingcols, repeatingrows — опциональные атрибуты, работающие совместно с атрибутом worksheet. Задаёт для нового листа колонтитульные (повторяющиеся при печате на каждом листе) колонки/строки. Значения следует указывать в формате "1:2" с нумерацией С НУЛЯ (например, чтоб повторять на каждой странице первую строку, надо указать repeatingrows="0:0")
  • pagebreak — если присутствует данный атрибут в виде pagebreak="true", то вывод следующей секции отчёта начнётся с новой страницы. При этом, если текущий режим вывода — сверху вниз, то формируется горизонтальный разрыв страницы, а если слева направо -- то вертикальный разрыв страницы. Иногда недопустимы «висячие строки» в отчёте (чаще всего это относится к элементам «подвала» с итогами и подписями). Если Xylophone-отчёт создаётся для моментального вывода на печать (без ручной подгонки), то разбивка на страницы сразу же должна быть выполнена корректно.

Репрезентативный пример

Предположим, что необходимо сформировать отчёт, состоящий из титульного листа и произвольного количества листов-разделов (имена этих листов определеляются данными). Допустим, на титульном листе находится иерархический список элементов, находящихся на разном уровне и требующих различное оформление:

Excel1.png

В разделах, которых может быть произвольное количество, определяемое данными шаблона, находится сводная таблица, количество строк и столбцов которой переменно:

Excel2.png

Данные представлены в XML-файле, имеющем следующий вид:

<?xml version="1.0" encoding="UTF-8"?>
<report>
    <titlepage>
        <line name="Строка 1" value="10"/>
        <group name = "Строка 2" value = "23">
            <line name = "Строка 2.1" value="30"/>
            <line name = "Строка 2.2" value="92"/>
        </group>
        <line name = "Строка 3" value="11"/>
    </titlepage>
    <sheet name="Раздел А">
        <column name="2009"/>
        <column name="2010"/>
        <column name="2011"/>
        <row name="Позиция 1">
            <cell value = "1"/>
            <cell value = "33"/>
            <cell value = "34"/>
        </row>
        <row name="Позиция 2">
            <cell value = "93"/>
            <cell value = "9"/>
            <cell value = "1"/>
        </row>
        <row name="Позиция 3">
            <cell value = "1"/>
            <cell value = "50"/>
            <cell value = "2"/>
        </row>
    </sheet>
    <sheet name="Раздел Б">
        
    </sheet>
</report>

В этом случае, шаблон, содержащий оформление и поля подстановки для титульного листа и разделов, может иметь вид:

Excel3.png

Подстановочные поля имеют формат

~{Xpath-выражение} 

(тильда, фигурная скобка, Xpath-выражение относительно контекста XML, закрывающая фигурная скобка).

Важная информация
Умение грамонтно писать XPath-выражения, извлекающие информацию из текущего контекста XML-файла, является ключевым для успешного создания отчётов в технологии Xylophone. Если вы не знакомы с XPath, изучить его можно, например, здесь: http://www.w3schools.com/xpath/

В Xpath-выражении, помимо стандартного синтаксиса, можно применять следующие специальные функции:

  • сurrent() --- при обработке меняется на полный XPath-путь к текущей ноде. Полный аналог функции current() в XSLT. Необходимо для сложных XPath-выражений, наличие этой функции оправдано по той же причине, что наличие current() в XSLT (см. документацию по XSLT о функции current() и её отличию от . (точки)).
  • position() --- номер итерации. При обработке меняется на номер прохода по iteration. Решает задачу простой последовательной нумерации пунктов в отчёте (в случаях, когда данная функция подходит, не нужно включать нумерацию в тэги файла данных).

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

Файл-дескриптор, управляющий обходом XML, может иметь следующий вид:

<?xml version="1.0" encoding="UTF-8"?>
<element name="report">
    <!-- Вывод единственного титульного листа, с иерархией -->
    <iteration index="0">
        <element name="titlepage">
            <!-- Статическое название листа -->
            <output worksheet="Титульный" range="A3:B4"/>
            <iteration>
                <element name="line">
                    <output range="A5:B5"/>
                </element>
                <element name="group">
                    <output range="A6:B6"/>
                    <iteration>
                        <element name="line">
                            <output range="A7:B7"/>
                        </element>
                    </iteration>
                </element>
            </iteration>
        </element>
    </iteration>
    <!-- Вывод всех прочих листов, со сводными таблицами -->
    <iteration>
        <element name="sheet">
            <!-- Динамическое название листа -->
            <output worksheet="~{@name}"/>
            <!-- И за ним слева направо заголовки столбцов -->
            <iteration mode="horizontal">
                <element name="(before)">
                    <!-- Выводим пустую ячейку в ЛВУ сводной таблицы -->
                    <output sourcesheet="src" range="A11"/>
                </element>
                <element name="column">
                    <output sourcesheet="src" range="B11"/>
                </element>
            </iteration>
            <!-- Выводим строки: итерация с режимом вывода умолчанию, сверху вниз -->
            <iteration>
                <element name="row">
                    <!-- И по строке - слева направо -->
                    <iteration mode="horizontal">
                        <!-- Заголовок строки -->
                        <element name="(before)">
                            <output range="A12"/>
                        </element>
                        <element name="cell">
                            <output range="B12"/>
                        </element>
                    </iteration>
                </element>
            </iteration>
        </element>
    </iteration>
</element>

SAX-режим для огромных отчётов

При необходимости быстрого построения отчётов с очень большим количеством данных реализован SAX-режим обработки файла с исходными данными. Данный режим подразумевает, что файл с данными никогда не загружается в память целиком и формирование результирующего файла управляется SAX-событиями, чем достигается высокая скорость и экономия памяти, позволяющая обрабатывать огромные массивы данных. Использование SAX-режима можно указать в параметрах запуска Xylophone (по умолчанию режим запуска — DOM). SAX-режим подразумевает следующие структурные ограничения:

  1. Только один тэг <iteration> внутри каждого из тэгов <element>.
  2. XPath-ссылки могут быть только ссылками на атрибуты текущего элемента. Поддерживается функция position().

Переструктурировав соответствующим образом XML-файл с данными, можно добиться выполнения п. 1 и 2 для широкого спектра задач (в частности, легко можно переструктурировать XML для «репрезентативного примера»).

Второй этап: формирование PDF-файлов и вывод на печать

При помощи модуля Excel2Print получившийся Excel-отчёт можно преобразовать в PDF-формат или сразу же вывести на печать.

Важная информация


Внимание: так можно поступить только с XLS-файлом, с XLSX-форматом система пока не работает.

Внимание: возможности выводить в PDF/принтер картинки и графики нет и в настоящий момент такая возможность даже не рассматривается!

Использование модуля Excel2Print

Рекомендованный паттерн следующий:

    import java.io.File as File 
    import java.io.FileInputStream as FileInputStream
    import java.io.FileOutputStream as FileOutputStream
 
    #Библиотеки Xylophone и Excel2Print
    import ru.curs.xylophone.XML2Spreadsheet as XML2Spreadsheet
    import ru.curs.xylophone.Excel2Print as Excel2Print
 
    #Готовим файлы и потоки
    data=FileInputStream(datafile) 
    template=File(parameters.flutepath + 'templates/universal_transfer_document.xls')
    descriptor=File(parameters.flutepath + 'templates/universal_transfer_document.xml')
 
    #получаем объект-книгу при помощи нового метода toPOIWorkbook
    workbook = XML2Spreadsheet.toPOIWorkbook(data, descriptor, template, False)
 
    #инициируем конвертер Excel2Print созданной книгой
    e2p = Excel2Print(workbook)
    #Прописываем путь к файлу конфигурации
    e2p.setFopConfig(parameters.flutepath + "c:/temp/fop.xconf");
    #Прописываем имя принтера в операционной системе (если это необходимо)
    e2p.setPrinterName("My LaserJet Printer")
 
    #Конверсия в PDF
    pdfresult=FileOutputStream('C:/temp/result.pdf')
    try:
        e2p.toPDF(pdfresult)
    finally:
        pdfresult.close()
 
    #Вывод на принтер (принтер операционной системы, настроенный по умолчанию)
    e2p.toPrinter()

Известные особенности и альтернативный метод вывода на печать

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

Быстро и качественно вывести PDF-файл на печать, не используя Acrobat Reader, можно при помощи Open Source системы GhostScript + GhostView (www.ghostscript.com). Команда

    gsprint myfile.pdf

выбрасывает PDF-файл на принтер, а также имеет множество дополнительных параметров.