Главная » XSLT » Использование встроенных расширений Saxon и Xalan

0

Задача

Вы хотите воспользоваться какими-то полезными расширениями, реализо­ванными в этих популярных процессорах XSLT.

Решение

XSLT 1.0

Этот рецепт разбит на ряд мини-рецептов, иллюстрирующих применение наиболее интересных расширений в Saxon и Xalan. Во всех примерах префикс saxon ассоциирован с пространством имен http://icl.com/saxon, а префикс xalan – с http://xml.apache.org/xslt.

Требуется организовать вывод в несколько файлов

Мы уже несколько раз встречались в этой книге с реализованным в Saxon меха­низмом вывода результатов в несколько файлов. Для этого предназначен элемент saxon:output. Имеется также элемент xsl:document, но он будет работать лишь, если атрибут version в таблице стилей равен «1.1», а потому его следует избегать. Выходной файл определяется атрибутом href, который может быть вы­числяемым выражением:

<saxon:output href="toc.html"> <html>

<head><title>Oглавление</title></head> <body>

<xsl:apply-templates mode="toc" select="*"/> </body> </html>

</saxon:output>

В Xalan принят совершенно другой подход к выводу нескольких файлов. Вместо одной команды Xalan предоставляет целых три: redirect:open, redirect:close и redirect:write. С этими элементами расширения ассоциировано пространство именxmlns:redirect = "org.apache.xalan.xslt.extensions.Redirect". В типичных случаях можно обойтись одним элементом redirect:write, по­скольку в отсутствие других команд он откроет файл, запишет в него данные и закроет.

У каждого из упомянутых элементов есть атрибут file и/или select для указания выходного файла. Значением атрибута file является строка, так что с его помощью имя файла можно задать непосредственно. Атрибут select в каче­стве значения принимает выражение XPath и, стало быть, позволяет формировать имя файла динамически. Если включить оба атрибута, то расширение redirect сначала вычисляет значение select, а если при этом не получается допустимое файла, обращается к атрибуту file.

<xalan:write file="toc.html"> <html>

<head><title>Oглавление</title></head> <body>

<xsl:apply-templates mode="toc" select="*"/> </body> </html> </xalan:write>

Пользуясь расширенными возможностями Xalan, вы можете переключиться с основного файла на дополнительные, оставив основной файл открытым. Это, правда, идет вразрез с заложенным в XSLT принципом работы без побочных эф­фектов, но вероятно, Xalan обеспечивает предсказуемость результата:

<xsl:template match="doc"> <xalan:open file="regular.xml"/>

<xsl:apply-templates select="*"/> <xalan:close file="regular.xml"/> <xsl:template/>

<xsl:template match="regular">

<xalan:write file="regular.xml"> <xsl:copy-of select="."/> </xalan:write/> </xsl:template>

<xsl:template match="*">

<xsl:variable name="file" select="concat(local-name( ),’.xml’)"/> <xalan:write select="$file">

<xsl:copy-of select="."/> </xalan:write/> </xsl:template>

В XSLT 2.0 поддержка вывода в несколько файлов уже встроена в виде нового элемента xsl:result-document:

<xsl:result-document format="html" href="toc.html"> <html>

<head><title>Оглaвление</title></head> <body>

<xsl:apply-templates mode="toc" select="*"/> </body> </html> </xsl:result-document>

Вы хотите расщепить сложное преобразование на несколько более простых, выполняемых последовательно

Разработчики, привыкшие работать в системе Unix, хорошо знакомы с кон­вейерной обработкой, когда выход одной команды подается на вход другой. Этот механизм имеется и в других операционных системах, в том числе в Windows. Ге­ниальность этой идеи состоит в том, что она позволяет решить сложную задачу, разбив ее на более простые.

Поскольку XSLT-преобразование – это в конечном итоге преобразование од­ного дерева в другое, то применение конвейера выглядит вполне естественно. Здесь результат одного преобразования становится исходным деревом для следу­ющего. Мы уже не раз встречались с подобными примерами, когда функция рас­ширения node-set() создавала промежуточные наборы узлов, обрабатываемые на более поздних стадиях. Saxon предоставляет альтернативный способ с помо­щью атрибута saxon:next-in-chain элемента xsl:output. Этот атрибут пере­направляет выход в другую таблицу стилей. Его значением является URL таблицы, которая должна обрабатывать выходной поток. Этот поток должен содержать корректно сформированный XML, а атрибуты, управляющие форматом вывода (например, method, cdata-section-elements) игнорируются. Вторая табли­ца стилей выводит данные в то место, куда выводила бы первая, если бы не было атрибута saxon:next-in-chain.

В Xalan к аналогичной функциональности принят иной подход; применяется элемент расширения pipeDocument, обладающий интересной особенностью – его можно включить в не содержащую больше ничего таблицу стилей и создать тем самым конвейер между независимыми таблицами, ничего не знающими о су­ществовании друг друга. Таким образом реализация Xalan гораздо ближе к кон­вейеру Unix, так как информация о конвейере не «зашивается» в участвующие в нем таблицы стилей. Предположим, что таблица strip.xslt удаляет из XML-доку- мента, описывающего книгу, какие-то элементы, а таблица contents.xslt создает ог­лавление на основе иерархической структуры. Тогда их можно следующим обра­зом объединить в конвейер:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:pipe="xalan://PipeDocument" extension-element-prefixes="pipe">

<xsl:param name="source"/> <xsl:param name="target"/>

<!–Список сохраняемых элементов. Bee остальные удаляются. –> <xsl:param name="preserve-elems"/>

<pipe:pipeDocument source="{$source}" target="{$target}">

<stylesheet href="strip.xslt">

<param name="preserve-elems" value="{$preserve-elems}"/> </stylesheet>

<stylesheet href="contents.xslt"/> </pipe:pipeDocument>

</xsl:stylesheet>

Этот код создаст оглавление только из оставшихся элементов, не жертвуя не­зависимостью таблиц strip.xsl и contents.xsl.

Требуется работать с датами и временем

В главе 4 приведено много рецептов для работы с датами и временем, но в XSLT нет способа получить текущую дату и время. И в Saxon, и в Xalan реали­зованы определенные в проекте EXSLT функции из модуля, относящегося к дате и времени. В этом разделе мы для справки приведем соответствующую докумен­тацию. Все функции перечислены в таблице 14.1 с указанием возвращаемого значения и аргументов. Вопросительным знаком обозначаются необязательные аргументы.

Таблица 14.1. Функции для работы с датами и временем, определенные в EXSLT

Функция                                                            Поведение

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:date="http://exslt.org/dates-and-times">

<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/"> <html>

<head><title>MoH безыскусная домашняя CTpaHMaa</title></head> <body>

<Ъ1>Моя безыскусная домашняя страница</Ъ1>

<div>Ceft4ac <xsl:value-of select="date:time( )"/> <xsl:value-of select="date:date( )"/>, и эта страница такая же скучная, как вчера.<^^> </body> </html>

</xsl:template>

В XSLT 2.0 поддержка даты и время уже встроена (см. главу 4), так что эти расширения не нужны.

Требуется более эффективная реализация операций над множествами

В главе 9 рассматривались различные способы реализации теоретико-множе­ственных операций, помимо объединения, которое XPath и так поддерживает с помощью оператора |. Эти решения и не самые эффективные, и не слишком на­глядные.

И Saxon, и Xalan решают проблему путем реализации операций над множе­ствами, определенных в модуле set проекта EXSLT (см. таблицу 14.2).

Таблица 14.2. Операции над множествами, определенные в модуле set проекта EXSLT

 

 

Таблица 14.2. Операции над множествами, определенные в модуле set проекта EXSLT (окончание)

Функция set:distinct дает удобный способ удаления дубликатов в пред­положении, что равенство узлов определено как равенство их строковых значений:

<xsl:varible name="firstNames" select="set:destinct(person/firstname)"/>

Функции set:leading и set:trailing могут извлекать узлы, заключен­ные между двумя заданными. Например, в рецепте 12.9 использовалось сложное выражение для поиска узлов xslx:elsif и xslx:else, ассоциированных с уз­лом xslx:if. Эти расширения позволяют решить задачу проще:

<xsl:apply-templates

select="set:leading(following-sibling::xslx:else | following-sibling::xslx:elsif, following-sibling::xslx:if)"/>

Здесь говорится, что надо отобрать всех братьев типа xslx:else и xslx:elseif, которые следуют за текущим узлом, но предшествуют следующему узлу xslx:if.

Требуется получить расширенную информацию об узле во входном дереве

В Xalan есть функции, которые позволяют получить информацию о положении узлов во входном дереве. Saxon 6.5.2 предоставляет только функции saxon:systemId и saxon:lineNumber. Применяются они, в частности, для отладки. Чтобы ими вос­пользоваться, нужно установить в true атрибут source_location элемента TransformerFactory. Сделать это позволяет флаг -L в командной строке или метод TransformerFactory.setAttribute( ).

systemId( )

systemld(node-set)

Возвращают соответственно системный идентификатор текущего узла и пер­вого узла в наборе.

lineNumber( ) lineNumber(node-set)

Возвращают соответственно номер строки исходного документа, соответству­ющий текущему узлу и первому узлу в наборе. Если номер строки неизвестен (на­пример, когда на вход подан документ DOM), возвращают -1.

columnNumber( )

columnNumber(node-set)

Возвращают соответственно номер колонки исходного документа, соответ­ствующий текущему узлу и первому узлу в наборе. Если номер колонки неизвес­тен (например, когда на вход подан документ DOM), возвращают -1.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" xmlns:info="xalan://org.apache.xalan.lib.NodeInfo">

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="foo">

<xsl:comment>foo найдено в строке <xsl:value-of

select="info:lineNumber( )"/> и колонке <xsl:value-of select="info:columnNumber( )"/>.</xsl:comment> <!– … –> </xsl:template>

</xsl:stylesheet>

Требуется обратиться к реляционной базе данных

Интерфейс XSLT с реляционными базами данных открывает широчайший спектр новых возможностей. И Saxon, и Xalan поддерживают SQL с помощью расширений. Если вы пишете таблицы стилей, которые модифицируют базу дан­ных, то нарушаете принцип работы без побочных эффектов.

 Вот что говорит Майкл Кэй по поводу расширений SQL в Saxon: «Не пред­полагается, что это программное обеспечение промышленного качества (в проекте заложено много ограничений). Скорее, это иллюстрация того, как можно использовать элементы расширения для обогащения возмож­ностей процессора».

В Saxon для доступа к базе данных есть пять элементов расширения: sql:connect, sql:query, sql:insert, sql:column и sql:close. Они по­нятны любому, кто работал с реляционными базами через ODBC или JDBC.

<sql:connect driver="jdbc-driver" database="db name" user="user name"

password="user password"/>

Открывает соединение с базой данных. Любой атрибут может быть вычисляе­мым выражением. Атрибут driver – это имя класса драйвера JDBC, а database должно быть именем, которое JDBC может ассоциировать с реальной базой данных.

<sql:query table="the table" column="column names" where="where clause" row-tag="row element name" column-tag="column element name" disable-output- escaping="yes or no"/>

Выполняет запрос и записывает результаты в выходное дерево, используя элементы для представления строк и колонок. Имена этих элементов задаются с помощью атрибутов row-tag и col-tag соответственно. Атрибут column мо­жет содержать список колонок, а если он равен *, выбираются все колонки.

<sql:insert table="table name">

Выполняет предложение SQL INSERT. Дочерние элементы (sql:column) задают данные, которые нужно добавить в таблицу.

<sql:column name="col name" select="xpath expr"/>

Должен быть дочерним элементом sql:insert. Значение можно задать в ат­рибуте select или путем вычисления дочерних элементов sql:column. В обоих случаях разрешается использовать только строковые значения. Никакого способа работы со стандартными типами данных SQL не предусмотрено.

Xalan обеспечивает более развитую поддержку SQL, нежели Saxon. В этой главе мы рассмотрим только основы. В разделе «См. также» приведены ссылки на более подробную информацию. В отличие от Saxon, в Xalan для доступа к базе данных применяются функции расширения.

sql:new(driver, db, user, password)

Устанавливает соединение.

sql:new(nodelist)

Устанавливает соединение, используя информацию, находящуюся во вход­ном XML-документе или в таблице стилей. Например:

<DBINFO>

<dbdriver>org.enhydra.instantdb.jdbc.idbDriver</dbdriver> <dburl>jdbc:idb:../../instantdb/sample.prp</dburl> <user>jbloe</user> <password>geron07moe</password> </DBINFO>

<xsl:param name="cinfo" select="//DBINFO"/> <xsl:variable name="db" select="sql:new($cinfo)"/>

sql:query(xconObj, sql-query)

Отправляет запрос базе данных. Объект xconObj – это результат функции new(). Функция возвращает потоковый результирующий набор в виде узла row-set.

Можно перебирать набор строк по одной. При каждом обращении возвращается элемент row, так что можно начинать преобразовывать набор строк еще до того, как он получен полностью.

sql:pquery(xconObj,sql-query-with-params)

sql:addParameter(xconObj, paramValue)

sql:addParameterFromElement(xconObj,element)

sql:addParameterFromElement(xconObj,node-list)clearParameters(xconObj)

Используются совместно для реализации параметризованных запросов. Па­раметры представляются символами ? в тексте запроса. Различные варианты функции addParameter( ) подставляют вместо них фактические значения еще до начала выполнения запроса. Чтобы стереть старые значения параметров, пользуйтесь функцией clearParameters( ).

sql:close(xconObj)

Закрывает соединение с базой данных.

Функции sql: query ( ) и sql:pquery() возвращают узел типа Document, который содержит массив элементов column-header, один элемент row, который используется повторно, и массив элементов col. Каждый элемент column-header (по одному на колонку в наборе строк) содержит атрибут ColumnAttribute для каждого дескриптора колонки в объекте ResultSetMetaData. Каждый элемент col содержит текстовый узел с представлением значения этой колонки в текущей строке.

Дополнительную информацию об использовании XSLT для доступа к реля­ционным базам данных см. в книге Doug Tidwell XSLT (O’Reilly, 2001).

Требуется динамически вычислить выражение XPath, созданное на этапе выполнения

И в Saxon, и в Xalan есть очень мощная функция расширения evaluate, ко­торая принимает на входе строку и вычисляет ее как выражение XPath. В проекте EXSLT.org также определена более переносимая функция dyn:evaluate(). Та­кое средство рассматривалось при работе над XSLT 2.0, но в тот момент рабочая груп­па решила не включать ее в стандарт под предлогом, что динамическое вычисление «… оказывает значительное влияние на архитектуру процессора на этапе выпол­нения, а также на его способность производить статическую оптимизацию».

Динамические возможности удобны при создании таблично управляемой таб­лицы стилей. Следующая таблица стилей может представить информацию о лю­дях в табличном виде, но ее можно настроить на почти произвольный формат входного XML-документа путем простого изменения данных в таблице:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:saxon="http://icl.com/saxon"

xmlns:paths="http://www.ora.com/XSLTCookbook/NS/paths" exclude-result-prefixes="paths">

<!– Этот параметр служит для задания документа, содержащего таблицу, –> <!– которая описывает, как искать информацию о людях –> <xsl:param name="pathsDoc"/>

<xsl:template match="/"> <html> <head>

<title>Люди</title> </head> <body>

<!– Мы загружаем выражение XPath из таблицы [Symbol_Wingdings_22 4] —> <!– во внешнем документе –> <xsl:variable name="peoplePath"

select="document($pathsDoc)/*/paths:path[@type=’people’]/@xpath"/> <table> <tbody> <tr>

<th>Имя</th> <th>Фамилия</th> </tr>

<!– Динамически вычисляем выражение XPath, которое ищет –> <!– информацию о людях –>

<xsl:for-each select="saxon:evaluate($peoplePath)">

<xsl:call-template name="process-person"/> </xsl:for-each> </tbody> </table> </body> </html>

</xsl:template>

<xsl:template name="process-person"> <xsl:variable name="firstnamePath"

select="document($pathsDoc)/*/paths:path[@type=’first’]/@xpath"/> <xsl:variable name="lastnamePath"

select="document($pathsDoc)/*/paths:path[@type=’last’]/@xpath"/>

<tr>

<!– Динамически вычисляем выражение XPath, которое ищет –> <!– ту информацию о человеке, которую мы хотим обрабатывать –> <tdXxsl:value-of select="saxon:evaluate($firstnamePath)"/X/td> <tdXxsl:value-of select="saxon:evaluate($lastnamePath)"/></td>

</tr> </xsl:template>

Если данные о человеке представлены в виде элементов, то можно использо­вать такую таблицу:

<paths:paths xmlns:paths="http://www.ora.com/XSLTCookbook/NS/paths"> <paths:path type="people" xpath="people/person"/> <paths:path type="first" xpath="first"/> <paths:path type="last" xpath="last"/> </paths:paths>

А если в виде атрибутов, то такую:

<paths:paths xmlns:paths="http://www.ora.com/XSLTCookbook/NS/paths"> <paths:path type="people" xpath="people/person"/> <paths:path type="first" xpath="@first"/> <paths:path type="last" xpath="@last"/> </paths:paths>

Требуется изменить значение переменной

Возьмите чуть ли не любую книгу по XSLT, и вы прочтете, что невозможность изменить один раз присвоенное переменной или параметру значение – особен­ность XSLT, а не дефект. Это действительно так, поскольку позволяет предотвра­тить целый класс ошибок, делает таблицы стилей более понятными и открывает возможность для некоторых оптимизаций. Но иногда это очень неудобно. Saxon позволяет обойти это препятствие с помощью элемента расширения saxon:assign. Применять его можно только к переменным, которые описаны как изменяемые с помощью атрибута расширения saxon:assignable="yes":

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:saxon="http://icl.com/saxon"

extension-element-prefixes="saxon">

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:variable name="countFoo" select="0M saxon:assignable="yes"/>

<xsl:template name="foo">

<saxon:assign name="countFoo" select="$countFoo + 1"/>

<xsl:comment>Этo <xsl:value-of select="$countFoo"/>-brn вызов шаблона foo.</xsl:comment> </xsl:template>

<!– … –>

</xsl:stylesheet>

Требуется написать полноценную функцию в XSLT 1.0

Многие примеры в этой книге реализованы с помощью именованных шабло­нов, которые вызываются командой xsl:call-template. Часто такая реализация неудобна и некрасива, поскольку на самом деле хотелось бы обратиться к коду как к настоящей функции, вызываемой так же легко, как функции, встроенные в XPath. Эта возможность поддерживается в XSLT 2.0, а в 1.0 имеет смысл об­ратить внимание на EXSLT-расширение func:function. которое реализова­но в Saxon и в последней версии Xalan (версия 2.3.2 на момент написания кни­ги). Следующий код представляет собой шаблон из главы 2, реализованный в виде функции:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="http://exslt.org/functions"

xmlns:str="http://www.ora.com/XSLTCookbook/namespaces/strings" extension-element-prefixes="func">

<xsl:template match="/"> <xsl:value-of

select="str:substring-before-last(‘123456789a123456789a123′, </xsl:template>

<func:function name="str:substring-before-last"> <xsl:param name="input"/> <xsl:param name="substr"/>

<func:result>

<xsl:if test="$substr and contains($input, $substr)"> <xsl:variable name="temp"

select="substring-after($input, $substr)" /> <xsl:value-of select="substring-before($input, $substr)" /> <xsl:if test="contains($temp, $substr)"> <xsl:value-of

select="concat($substr,

str:substring-before-last($temp, $substr))"/>

</xsl:if> </xsl:if> </func:result> </func:function>

</xsl:stylesheet>

XSLT 2.0

Большинство расширений, имеющихся в Saxon 6, сохранились и в Saxon 8. Но некоторые, например saxon:function(), в XSLT 2.0 уже не нужны. Есть ряд функций, предназначенных для расширения возможностей XQuery, посколь­ку такие возможности уже имеются в XSLT. Так, функции saxon: index ( ) и saxon:find() по своему назначению аналогичны элементу xsl:key и функции key( ). Однако в Saxon 8 появился ряд новых расширений, которых не было в пре­дыдущей версии.

Требуется получить выражение XPath для доступа к текущему узлу

Функция saxon:path( ) не имеет аргументов и возвращает строку, содер­жащую выражение XPath, которое ведет к текущему узлу. Это аналог решения на XSLT, которое приведено в рецепте 15.2 для отладочных целей.

Требуется обрабатывать динамические ошибки

Во многих современных языках, например Java и C++, имеется механизм try- throw-catch для обработки динамических (возникающих на этапе выполнения) ошибок. В коммерческую версию Saxon (Saxon-SA) добавлена псевдофункция saxon:try, предоставляющая похожие, хотя и более ограниченные, возможно­сти. Она принимает в качестве первого аргумента выражение. Если при вычисле­нии этого выражения возникает динамическая ошибка (например, деление на 0, ошибка типизации и т.д.), то возвращается значение второго аргумента:

<xsl:template match="/"> <test>

<xsl:value-of select="saxon:try(*[0], ‘Индекс вне диапазона’)"/> </test>

</xsl:template>

Второй аргумент может быть строкой, содержащей сообщение об ошибке (как в примере выше), или некоторым значением по умолчанию. Майкл Кэй называет saxon:try псевдофункцией, потому что она не следует правилам, принятым для обычных функций в XPath. Точнее, она вычисляет второй аргумент, только если вычисление первого привело к ошибке.

Обсуждение

Использование расширений, реализованных конкретным производителем, – это палка о двух концах. С одной стороны, они позволяют писать на языке XSLT код, более простой или быстрый, чем было бы возможно, оставаясь в рамках стандарта. Иногда с их помощью можно даже сделать нечто, что на стандартном XSLT просто не реализуемо. С другой стороны, есть опасность накрепко связать себе с реализацией, будущее которой неопределенно.

Организация EXSLT.org убеждает разработчиков следовать единым соглаше­ниям о наиболее популярных расширениях, поэтому, если есть выбор, то безус­ловно следует предпочесть решение EXSLT, а не конкретного поставщика.

Другая тактика состоит в том, чтобы вообще избегать расширений, поставляе­мых в том или ином продукте, и делать все самому. В этом случае вы контролиру­ете исходный код и можете при необходимости перенести его на несколько про­цессоров. В рецептах 14.2, 14.3 и 14.4 приведены примеры таких доморощенных расширений.

См. также

В этой книге рассмотрены не все расширения, имеющиеся в процессорах Saxon и Xalan. Дополнительную информацию о расширениях Saxon можно найти на странице http://saxon.sourceforge.net/saxon6.5.4/extensions.html или http:// www.saxonica.com/ documentation/documentation.html (для XSLT 2.0), а о расшире­ниях Xalan – на странице http://xml.apache.org/xalan-j/extensionslib.html.

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии