Алексей Валиков - Технология XSLT
□ Возможность использования того или иного языка для написания расширений зависит от наличия интерфейса XSLT-процессора для этого языка. Не следует ожидать, что любой процессор сможет работать с расширениями, написанными на любом языке, то есть, иначе говоря, расширения привязывают преобразования к строго определенному процессору или, в лучшем случае, группе процессоров.
□ В то время как сам XSLT не имеет сторонних эффектов, расширения этого принципа придерживаться не обязаны. Вследствие этого преобразования, в которых есть расширения с побочными эффектами могут из-за различных методов обработки входящего документа генерировать на разных процессорах разный результат.
Итак, вопрос, использовать расширения или нет — это вопрос "функциональность против переносимости", и, хотя его решение будет всегда зависеть от конкретной задачи, существуют также и довольно общие критерии оценки, которые мы приведем в следующей таблице (табл. 10.1).
Таблица 10.1. Использование расширений: критерии за и против
Использовать расширения стоит, если: Использовать расширения не стоит, если: преобразования будут выполняться на заранее известном процессоре или группе процессоров; целевой процессор неизвестен. Преобразования должны быть переносимы, насколько это возможно; в XSLT нет средств для выполнения требуемой задачи, либо они очень неэффективны; в XSLT имеются средства для выполнения требуемой задачи; преобразование должно обладать побочными эффектами; преобразование может обойтись без побочных эффектов; целевой процессор предоставляет интерфейс для хорошо известного разработчику языка программирования интерфейс для нужного языка программирования в целевом процессоре отсутствуетПодводя итог, образно выражаясь, скажем, что расширения — это пушка, стрелять из которой по воробьям рекомендуется только, когда не остается ничего другого, или воробьи достаточно велики.
К сожалению, не представляется возможным описать в одной главе интерфейсы расширения даже наиболее распространенных XSLT-процессоров. Вместо этого мы постараемся изложить основные принципы создания расширения, а также приведем несколько общих примеров, которые смогут послужить основой для создания частных решений.
Основным языком реализации расширений, приводимых в этой главе, будет Java. Пожалуй, Java является единственным языком, интерфейсы расширений для которого достаточно стандартизированы, чтобы можно было говорить об общих подходах. Однако, если читатель не знаком с этим языком — ничего страшного, ведь основное внимание в этой главе уделяется использованию расширений в XSLT, а не написанию их в других языках и Java-код приводится только для того, чтобы сделать примеры рабочими.
Функции расширения
Прежде чем описывать использование функций расширения, вспомним, как мы вызывали в преобразованиях обычные функции, например, функцию concat:
<xsl:value-of select="concat('para', 'bellum')"/>
Атрибут select содержит XPath-выражение concat('para', 'bellum'), которое с точки зрения синтаксиса XPath является вызовом функции и соответствует продукции FunctionCall:
[XP16] FunctionCall ::= FunctionName
'(' ( Argument ( ',' Argument ) * ) ? ')'
Аргументами функции являются выражения, а имя может быть любым корректным XML-именем (за исключением node, comment, processing-instruction и text, которые используются для проверки типа узла):
[XP17] Argument ::= Expr
[XP35] FunctionName ::= QName - NodeType
В плане синтаксиса функции расширения ничем не отличаются от стандартных функций: они отвечают тем же самым грамматическим правилам. Единственное различие состоит в том, что функции стандартных библиотек XPath и XSLT принадлежат нулевому пространству имен, в то время как пространство имен функций расширения должно обязательным образом быть ненулевым. Иными словами, вызовы функций, имеющие вид имя(аргумент, аргумент, ...), будут считаться вызовами функций базовых библиотек, а вызовы вида префикс:имя(аргумент, аргумент, ...) будут считаться вызовами функций расширения.
ПримерВыражение
round(0.6)
является вызовом функции базовой библиотеки XPath, в то время как выражение
math:round(0.6)
является вызовом функции расширения.
Практически во всех процессорах пространство имен функции расширения является звеном, которое связывает ее с конкретной реализацией.
Пример<xsl:value-of select="math:round(0.6)" xmlns:math="java:java.lang.Math"/>
Элемент xsl:value-of вычисляет выражение math:round(0.6), которое является вызовом функции расширения. Само имя функции состоит из локальной части round и префикса math, которому соответствует URI java:java.lang.Math. В большинстве XSLT-процессоров вызов такого рода будет означать обращение к статической функции round класса java.lang.Math.
Простейшим случаем использования расширений в XSLT-процессорах, написанных на Java, является обращение к стандартным функциям пакетов Java.
ПримерПредположим, что входящий документ описывает координаты множества точек, а преобразование создает SVG-документ, содержащий линии, которые их последовательно соединяют.
ПримечаниеSVG — это XML-язык для описания масштабируемой векторной графики (от англ. scalable vector graphics). SVG позволяет простым XML-синтаксисом описывать векторную графику. SVG-документы могут показываться в браузерах при помощи таких компонент, как Adobe SVG Viewer или Batik от Apache XML Project.
Листинг 10.3. Входящий документ<?xml version="1.0" encoding="windows-1251"?>
<точки width="200" height="200">
<точка x="-50" y="-50"/>
<точка x=" 50" y="-50"/>
<точка x=" 50" y=" 50"/>
<точка x="-50" y=" 50"/>
</точки>
Листинг 10.4. Преобразование<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/2000/svg">
<xsl:output
indent="yes"
doctype-public="-//W3C//DTD SVG 1.0//EN"
doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
<xsl:template match="/">
<svg width="200" height="200">
<desc>Simple line-based figure</desc>
<xsl:apply-templates select="точки"/>
</svg>
</xsl:template>
<xsl:template match="точки">
<g style="stroke:black; stroke-width:2">
<xsl:apply-templates select="точка"/>
</g>
</xsl:template>
<xsl:template match="точка">
<line
x1="{@x + 100}"
y1="{@y + 100}"
x2="{following-sibling::точка[1]/@x + 100}"
y2="{following-sibling::точка[1]/@y + 100}">
<xsl:if test="position() = last()">
<xsl:attribute name="x2">
<xsl:value-of
select="preceding-sibling::точка[last()]/@x + 100"/>
</xsl:attribute>
<xsl:attribute name="y2">
<xsl:value-of
select="preceding-sibling::точка[last()]/@y + 100"/>
</xsl:attribute>
</xsl:if>
</line>
</xsl:template>
</xsl:stylesheet>
Результатом этого преобразования является следующий SVG-документ.
Листинг 10.5. Выходящий SVG-документ<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<desc>Simple line-based figure</desc>
<g style="stroke:black; stroke-width:2">
<line x1="50" y1="50" x2="150" y2="50"/>
<line x1="150" y1="50" x2="150" y2="150"/>
<line x1="150" y1="150" x2="50" y2="150"/>
<line x1="50" y1="150" x2="50" y2="50"/>
</g>
</svg>
На рис. 10.1 приведен пример визуального представления этого документа.
Рис. 10.1. Визуальное представление полученного SVG-документа
Предположим теперь, что нам нужно не просто создать по данному множеству точек набор соединяющих их линий, но еще и произвести некоторые геометрические преобразования, например поворот на заданный в градусах угол α.
Формулы преобразования координат при повороте чрезвычайно просты:
x = x'∙cos(α) − y∙sin(α),
у = x'∙sin(α) + x'∙cos(α),