В этой статье мы познакомим вас с потоковыми XML-преобразованиями (STX): это язык для создания шаблонов, управляющих XML-преобразованиями, который работает с потоками из событий SAX. STX похож на XSLT 1.0 — язык для создания иерархических шаблонов, управляющих XML-преобразованиями; но в некоторых приложениях, благодаря ряду своих уникальных возможностей, STX оказывается более удобным.
Популярность XSLT росла на протяжении трёх прошедших с его создания лет; отчасти причиной, а отчасти следствием этого было всё более широкое применение XML. В отличие от объектной модели документа (DOM), предоставляющей программисту набор API-функций для работы с XML, XSLT — это нестрого типизированный декларативный язык, предназначенный специально для иерархических преобразований XML-документов. Несмотря на содержавшееся в стандарте XSLT 1.0 предписание:
…XSLT не предполагает использование в качестве полностью универсального языка для XML-преобразований: в основном он разрабатывался для тех видов преобразований, которые необходимы при использовании XSLT в составе XSL.
— XSLT получил широкое распространение именно в качестве универсального инструмента для обработки XML-документов.
SAX — это событийно-ориентированный аналог DOM API, а STX — потоковый аналог XSLT, в котором сохранены многие уже привычные элементы (например, шаблоны и язык выражений, похожий на XPath 1.0), но в качестве нижележащего интерфейса к содержимому XML-документа используется SAX. Про STX нужно сделать ту же оговорку, что уже сделана про XSLT: STX — это не универсальный язык XML-преобразований; кроме того, это не замена для XSLT, и не его новая улучшенная версия.
STX, как и SAX, — это полностью открытый для широкой публики проект, начатый Петром Кимпричем. Рассылка, посвящённая этому проекту, и текущая версия спецификации STX, содержащая полный список его участников, размещены на SourceForge. В настоящее время существуют две реализации STX:
В этом разделе некоторые из сильных сторон STX будут проиллюстрированы на конкретном примере. Представим себе питомник, в котором разводятся различные виды деревьев и цветов; заказы питомнику поступают в простом XML-формате. Ниже приведён сокращённый пример документа с заказами; его полный текст доступен непосредственно в виде XML. Каждый заказ (order) состоит из названия (name), необязательного количества (quantity) со значением по умолчанию 1, и ценой (price) одного экземпляра.
|
Начальная задача — создать преобразование, которое бы вычисляло полную стоимость заказанных растений (сложенные по всем заказам произведения количества на цену экземпляра). Эту задачу можно выполнить при помощи XSLT, рекурсивно применяя именованные шаблоны с параметрами.
Здесь можно взять XML-текст этого преобразования, а здесь — результат его работы в виде аккуратно отформатированного HTML.
Результат работы преобразования, как и ожидалось, — это XHTML-документ, в котором в таблицу сведена информация обо всех заказах, и вычислена их полная стоимость.
Чтобы выполнить преобразование при помощи Joost в среде JDK 1.4 (SAX-парсер, входящий в комплект JDK 1.4, — а это одна из версий Crimson, — далёк от совершенства; в качестве универсального XML-парсера лучше использовать Ælfred2 или Xerces-J2), загрузите дистрибутивы Joost и log4j, и затем выполните строку:
|
Преобразование должно завершиться почти мгновенно: на обычном ПК производительность Joost при выполнении приведённого здесь преобразования составляет около 1 МБ/сёк (эта оценка получена при обработке примера из 250 000 заказов).
В среде JDK 1.3 дополнительно потребуется совместимый с JAXP SAX-парсер, установленный в classpath.
Чтобы выполнить преобразование при помощи STX::XML в среде Perl, установите этот модуль и необходимые ему Perl-модули из CPAN: XML::SAX и XML::NamespaceSupport. Также рекомендуется установить модули XML::SAX::Expat и XML::SAX::Writer, хотя это необязательно. Затем выполните скрипт stxcmd.pl, расположенный в каталоге, куда установлен STX::XML:
|
Одного взгляда на наше преобразование достаточно, чтобы заметить его определённые сходства с XSLT:
Усугубляет это сходство тот факт, что имена многих элементов в пространстве имён STX (http://stx.sourceforge.net/2002/ns) совпадают с именами в пространстве имён XSLT 1.0 http://www.w3.org/1999/XSL/Transform); что более важно, эти элементы выполняют те же функции, что и соответствующие им элементы XSLT 1.0.
|
Преобразование заключено в элементе transform. (STX не поддерживает синтаксис, описанный в разделе 2.3 спецификации XSLT 1.0: «литерал в качестве преобразования».) |
|
В STX поддерживаются как глобальные, так и локальные переменные. Однако переменные в STX и XSLT 1.0 различаются по своему характеру. |
|
В STX для управления ходом преобразования используются декларативные шаблоны, сопоставляемые с документом по определённым правилам. (Эти правила в STX и XSLT 1.0 различаются в нескольких отношениях, как будет объяснено далее.) |
|
В STX есть элемент value-of, помещающий в результат значение выражения. |
|
В STX для выражений используется нестрого типизированный синтаксис, основанный на XPath 2.0; в основном этот синтаксис похож на синтаксис XPath 1.0, используемый в выражениях в XSLT 1.0. |
Кроме этого, в STX есть элементы choose, if, param и with-param, которые совпадают по назначению с одноимёнными элементами в XSLT 1.0.
Самое важное различие между XSLT и STX — это различие между узлами в XPath 1.0 и событиями в SAX. Основной и неделимый объект, с которым работает XPath 1.0 — узел, существующий в контексте содержащего его документа и отношений с другими узлами (родители, дети и т.д.) И напротив, основной объект, с которым работает SAX — событие, существующее вне всякого дополнительного контекста.
Программирование на уровне событий SAX нетривиально; обычно оно требует отслеживания для каждого события его контекста, сопоставление событию обработчика в соответствии с этим контекстом, и только затем — собственно обработку. STX реализует для потока событий SAX тонкую контекстную оболочку, и в результате узлы, поддерживаемые STX, оказываются достаточно мощными, чтобы могло работать множество полезных XPath-выражений, образующее собственный язык «STXPath», — и для этого не требуется создавать никакую иерархическую структуру наподобие DOM. Контекст узла в STXPath включает в себя:
Текущий узел. Это текущее событие и вся его внутренняя информация; STX сам объединяет последовательные события characters() в один узел.
Цепочка предков. Она состоит из всех предков текущего узла — т.е. из его самого и всех событий startElement(), для которых ещё не было соответствующего события endElement(), — вместе с их контекстами.
Счётчик позиции. Он отслеживает положение текущего узла среди других узлов с тем же родителем, учитывая их тип и части имён (локальное имя, префикс, и полное имя). Значение этого счётчика возвращается функцией position().
Заглядывание вперёд. STX всегда заглядывает на один узел вперёд, чтобы включить в контекст текущего элемента значение следующего за ним события characters() (если оно есть). Это значение становится значением текущего узла.
В STX нет аналога инструкции XSLT apply-templates, потому что события SAX поступают в STX в том порядке, в котором они расположены в документе: STX не позволяет произвольно выбирать узлы из объектной модели. Вместо этого в STX есть несколько различных инструкций process-*, управляющих обработкой следующих за ними событий SAX (с необязательным указанием group="…"; группы рассматриваются ниже).
Рисунок 1. Схема работы process-*
Можно представлять себе инструкции process-* как клапаны: они пропускают определённые события «сквозь себя» в другую часть преобразования; после того, как все пропущенные события будут обработаны, управление вернётся в текущий шаблон.
Инструкция process-children заставляет STX получить и обработать все дочерние узлы текущего узла (если они есть), и затем возвращает управление.
Инструкция process-attributes заставляет STX обработать все атрибуты текущего узла (если они есть).
Инструкция process-siblings заставляет STX обработать все узлы с тем же родителем, что текущий узел; допускаются необязательные выражения while="…" и until="…", задающие дополнительные условия возвращения управления. (При этом дочерние узлы исходного узла и все обработанные узлы с тем же родителем будут забыты, потому что STX не может возвращаться к уже обработанным узлам.) Вместе с возможностью раздельного создания событий startElement() и endElement() (см. ниже), это обеспечивает мощные возможности по переупорядочению содержимого XML-документов.
Инструкция process-self заставляет STX обработать текущий узел повторно — возможно, с использованием другой группы шаблонов.
В отличие от XSLT, не допускающего побочных эффектов, переменным в STX можно присваивать новые значения безо всяких ограничений — точно так же, как переменным в обычных языках программирования, таких как Java, C, Python и Perl. В нашем примере эта возможность использовалась для накопления суммы в ходе выполнения преобразования, а также для сохранения в переменных name, quantity и price значений, относящихся к каждому из заказов по очереди.
В нашем примере STX-преобразования можно увидеть некоторые сходства и различия между STX и XSLT 1.0; однако в STX есть масса дополнительных возможностей.
Раздельная генерация startElement() и endElement(). Так как STX работает с потоком событий, то возможно создавать и выводить события без их дальнейшей обработки; в частности, инструкции start-element и end-element создают в выходном потоке соответствующие события. Эта возможность позволяет структурировать бесструктурные документы — например, одним преобразованием можно сгруппировать заказы нашему питомнику по первой букве названия растения. (Вот какой HTML при этом получится.
Группы шаблонов. Шаблоны в STX можно объединять в группы элементами group. Можно включать и отключать шаблоны целыми группами; атрибут new-scope="…" в шаблоне управляет перекрытием в нём переменных группы.
Буферы событий. В STX существуют буферы — очереди для хранения событий. Их можно объявлять в преобразованиях или группах шаблонов по тем же правилам, что и обычные переменные. Чтобы заполнить буфер, нужно заключить часть шаблона в элемент result-buffer. Когда буфер заполнен, можно инструкцией process-buffer направить все события из него на вход преобразования — так, как будто бы эти события поступали из обрабатываемого документа.
Буферы удобно использовать для перестановки частей документа. (Например, Оливер Беккер реализовал пузырьковую сортировку на STX.)
Мы показали, что STX — это доступное средство для потоковых XML-преобразований. У этого языка уже есть две работоспособные реализации и активное сообщество разработчиков.