Книга: Технология XSLT

Функции расширения

Прежде чем описывать использование функций расширения, вспомним, как мы вызывали в преобразованиях обычные функции, например, функцию 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>
   <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>
  <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(?),

где x' и y' — старые координаты точки, x и y — новые координаты точки, а ? — угол поворота. Единственная загвоздка состоит в том, что функций sin и cos в базовой библиотеке XPath нет.

Самым простым выходом в такой ситуации является использование расширений. Например, в случае XSLT-процессора, который может использовать Java-расширения (Saxon, Xalan, Oracle XSLT Processor и так далее) надо будет лишь только объявить пространство имен вида:

xmlns:math="java:java.lang.Math"

и использовать функции math:sin и math:cos.

Листинг 10.6. Преобразование, осуществляющее поворот

<?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"
 xmlns:math="java:java.lang.Math">
 <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:param name="alpha" select="30"/>
 <xsl:variable name="alpha-radian" select="3.14 * ($alpha div 180)"/>
 <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>
   <xsl:apply-templates select="точка"/>
  </g>
 </xsl:template>
 <xsl:template match="точка">
  <xsl:variable name="x1" select="@x"/>
  <xsl:variable name="y1" select="@y"/>
  <xsl:variable name="x2r">
   <xsl:choose>
    <xsl:when test="position() = last()">
     <xsl:value-of select="preceding-sibling::точка[last()]/@x"/>
    </xsl:when>
    <xsl:otherwise>
     <xsl:value-of select="following-sibling::точка[1]/@x"/>
    </xsl:otherwise>
   </xsl:choose>
  </xsl:variable>
  <xsl:variable name="y2r">
   <xsl:choose>
    <xsl:when test="position() = last()">
     <xsl:value-of select="preceding-sibling::точка[last()]/@y"/>
    </xsl:when>
    <xsl:otherwise>
     <xsl:value-of select="following-sibling::точка[1]/@y"/>
    </xsl:otherwise>
   </xsl:choose>
  </xsl:variable>
  <xsl:variable name="x2" select="number($x2r)"/>
  <xsl:variable name="y2" select="number($y2r)"/>
  <line
   x1="{$x1 * math:cos($alpha-radian) -
        $y1 * math:sin($alpha-radian) + 100}"
   y1="{$x1 * math:sin($alpha-radian) +
        $y1 * math:cos($alpha-radian) + 100}"
   x2="{$x2 * math:cos($alpha-radian) -
        $y2 * math:sin($alpha-radian) + 100}"
   y2="{$x2 * math:sin($alpha-radian) +
        $y2 * math:cos($alpha-radian) + 100}"/>
 </xsl:template>
</xsl:stylesheet>

Результатом этого преобразования будет следующий документ.

Листинг 10.7. Результирующий 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"
 xmlns:math="java:java.lang.Math"
 width="200"
 height="200">
 <desc>Simple line-based figure</desc>
  <g>
  <line
   x1="81.68060041188197" y1="31.70359014757173"
   x2="168.29640985242827" y2="81.68060041188197"/>
  <line
   x1="168.29640985242827" y1="81.68060041188197"
   x2="118.31939958811803" y2="168.29640985242827"/>
  <line
   x1="118.31939958811803" y1="168.29640985242827"
   x2="31.70359014757173" y2="118.31939958811803"/>
  <line
   x1="31.70359014757173" y1="118.31939958811803"
   x2="81.68060041188197" y2="31.70359014757173"/>
 </g>
</svg>

Визуальное представление этого документа демонстрирует рис. 10.2, где представлен поворот, выполненный на 30°:


Рис. 10.2. Визуальное представление полученного SVG-документа

Анализируя полученный документ, мы можем заметить объявление пространства имен с префиксом math, которое было в него включено:

<svg
 xmlns="http://www.w3.org/2000/svg"
 xmlns:math="java:java.lang.Math"
 width="200"
 height="200">
 ...

Это тот самый случай, когда объявление пространства имен используется в самом преобразовании, но является лишним в выходящем документе. Для того чтобы избавиться от него, нужно просто включить префикс math в атрибут exclude-result-prefixes элемента xsl:stylesheet.

<xsl:stylesheet
 version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns="http://www.w3.org/2000/svg"
 xmlns:math="java:java.lang.Math"
 exclude-result-prefixes="math">
 ...

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

Листинг 10.8. Класс, вычисляющий координаты точки после поворота

package de.fzi.xslt;
public class rot {
 public static double X(double x, double y, double degree) {
  double radian = Math.PI * degree / 180;
  return x * Math.cos(radian) - y * Math.sin(radian);
 }
 public static double Y(double x, double y, double degree) {
  double radian = Math.PI * degree / 180;
  return x * Math.sin(radian) + y * Math.cos(radian);
 }
}

Для того чтобы использовать методы этого класса в качестве функций расширения, немного изменим объявления в элементе xsl:stylesheet:

<xsl:stylesheet
 version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns="http://www.w3.org/2000/svg"
 xmlns:rot="java:de.fzi.xslt.rot"
 exclude-result-prefixes="rot">

Создание элемента line теперь может быть записано в виде:

<line
 x1="{rot:X($x1, $y1, $alpha) + 100}"
 y1="{rot:Y($x1, $y1, $alpha) + 100}"
 x2="{rot:X($x2, $y2, $alpha) + 100}"
 y2="{rot:Y($x2, $y2, $alpha) + 100}"/>

Как мы отмечали выше, интерфейсы использования функций расширения весьма различаются между разными процессорами даже в случае такого переносимого языка, как Java. Отличия могут быть и в форме вызовов функций, и в форме объявлений пространств имен. Например, в процессоре Saxon пространство имен для класса de.fzi.xslt.rot может быть объявлено как:

xmlns:rot="java:de.fzi.xslt.rot"

в Xalan — как:

xmlns:rot="xalan://de.fzi.xslt.rot"

в Oracle XSLT Processor — как:

xmlns:rot="http://www.oracle.com/XSL/Transform/java/de.fzi.xslt.rot"

При этом сами вызовы во всех трех случаях будут одинаковыми:

rot:X($x, $y, $angle)

для метода X или

rot:Y($x, $y, $angle)

для метода Y.

Оглавление книги


Генерация: 0.049. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз