Главная » XSLT » Расширение XSLT с помощью JavaScript

0

Задача

Требуется реализовать на языке JavaScript функциональность, отсутствую­щую в XSLT.

Решение

В следующих примерах мы пользуемся имеющейся в процессоре Xalan-Java 2 возможностью вызывать программы на сценарных языках, например JavaScript. Обычно расширения на JavaScript пишутся для того, чтобы вызвать функцию, кото­рой нет ни в XSLT, ни в XPath. Типичный пример – тригонометрические функции:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt"

xmlns:trig="http://www.ora.com/XSLTCookbook/extend/trig"> <xsl:output method="text"/>

<xalan:component prefix="trig" functions="sin"> <xalan:script lang="javascript">

function sin (arg){ return Math.sin(arg);} </xalan:script> </xalan:component>

<xsl:template match="/">

Синус 45 градусов равен <xsl:text/>

<xsl:value-of select="trig:sin(3.14159265 div 4)"/> </xsl:template>

</xsl:stylesheet>

На JavaScript можно также реализовать функции с побочными эффектами и объекты, наделенные состоянием1:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt"

xmlns:count="http://www.ora.com/XSLTCookbook/extend/counter">

<xsl:output method="text"/>

<xalan:component prefix="count"

functions—’counter nextCount resetCount makeCounter"> <xalan:script lang="javascript">

function counter(initValue) {

this.value = initValue ;

}

function nextCount(ctr) {

return ctr.value++ ;

}

function resetCount(ctr, value) {

ctr.value = value ; return "" ;

}

function makeCounter(initValue) {

return new counter(initValue) ;

}

</xalan:script> </xalan:component>

<xsl:template match="/">

<xsl:variable name="aCounter" select="count:makeCounter(0)"/> Count: <xsl:value-of select="count:nextCount($aCounter)"/> Count: <xsl:value-of select="count:nextCount($aCounter)"/> Count: <xsl:value-of select="count:nextCount($aCounter)"/> Count: <xsl:value-of select="count:nextCount($aCounter)"/> <xsl:value-of select="count:resetCount($aCounter,0)"/> Count: <xsl:value-of select="count:nextCount($aCounter)"/> </xsl:template>

В большинстве реализаций этот код напечатает такую последовательность:

Count: 0 Count: 1 Count: 2 Count: 3 Count: 0

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

В примере ниже мы обращаемся к регулярным выражениям в JavaScript:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt"

xmlns:regex="http://www.ora.com/XSLTCookbook/extend/regex">

<xsl:output method="text"/>

<xalan:component prefix="regex"

functions="match leftContext rightContext getParenMatch makeRegExp"> <xalan:script lang="javascript">

function Matcher(pattern) {

this.re = new RegExp(pattern) ; this.re.compile(pattern) ; this.result="" ; this.left="" ; this.right="" ;

}

function match(matcher, input) {

matcher.result = matcher.re.exec(input) ; matcher.left = RegExp.leftContext ; matcher.right = RegExp.rightContext ; return matcher.result[0] ;

}

function leftContext(matcher) {

return matcher.left ;

}

function rightContext(matcher) {

return matcher.right ;

}

function getParenMatch(matcher, which) {

return matcher.result[which] ;

}

function makeRegExp(pattern) {

return new Matcher(pattern) ;

}

</xalan:script> </xalan:component>

<xsl:template match="/">

<xsl:variable name="dateParser"

select="regex:makeRegExp(‘(\d\d?)[/-](\d\d?)[/-](\d{4}|\d{2})’)"/> Сопоставлено: <xsl:value-of

select="regex:match($dateParser,

‘родился 05/03/1964 в Нью-Йорке.’)"/> Слева: <xsl:value-of select="regex:leftContext($dateParser)"/> Справа: <xsl:value-of select="regex:rightContext($dateParser)"/> Месяц: <xsl:value-of select="regex:getParenMatch($dateParser, 1)"/> День: <xsl:value-of select="regex:getParenMatch($dateParser,2)"/> Год: <xsl:value-of select="regex:getParenMatch($dateParser,3)"/> </xsl:template> </xsl:stylesheet>

Этот код выводит следующий результат:

Сопоставлено: 05/03/1964 Слева: Я родился Справа: в Нью-Йорке. Месяц: 05 День: 03 Год: 1964

Xalan также позволяет кодировать на JavaScript элементы расширения. Ниже приведен элемент, который повторяет свое содержимое n раз. Он мог бы быть по­лезен для дублирования строк или фрагментов структуры, а также в качестве про­стого способа организации циклов:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt"

xmlns:rep="http://www.ora.com/XSLTCookbook/extend/repeat" extension-element-prefixes="rep">

<xsl:output method="xml"/>

<xalan:component prefix="rep" elements="repeat">

<xalan:script lang="javascript"> <![CDATA[

function repeat(ctx, elem) {

// Получить значение атрибута n в виде целого числа

n = parseInt(elem.getAttribute("n")) ;

// Получить объект Transformer, необходимый

// для вычисления узлов

xformer = ctx.getTransformer( ) ;

// Вычислить содержимое повторяемого элемента n раз

for(var ii=0; ii < n; ++ii) {

node = elem.getFirstChild( ) ; while(node)

{

node.execute(xformer) ;

node = node.getNextSibling( ) ;

} }

// Возвращаемое значение вставляется в выходной документ; // чтобы предотвратить этот эффект, вернем null return null ;

}

]]>

</xalan:script> </xalan:component>

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

<!– Дублирование текста –>

<test1><rep:repeat n="10">a</rep:repeat></test1> <!– Дублирование структуры –> <test2>

<rep:repeat n="10"> <Malady>

<FirstPart>Shim’s</FirstPart>

<SecondPart>Syndrome</SecondPart> </Malady> </rep:repeat> </test2>

<!– Повторное выполнение XSLT-кода –>

<! — (собственно, то же самое делается в test1 и test2)—>

<test3>

<rep:repeat n="10">

<xsl:for-each select="*">

<xsl:copy/> </xsl:for-each> </rep:repeat> </test3> </tests> </xsl:template> </xsl:stylesheet>

Обсуждение

Создавать расширения на JavaScript (или ином встраиваемом сценарном языке) очень соблазнительно. Конечно, приходится мысленно переключаться с одного языка на другой, зато не нужно переходить в новую среду разработки и вызывать компилятор. Но, пожалуй, самое большое достоинство таких языков, как JavaScript или VBScript, – их простота1.

Но у использования расширений на сценарных языках есть и минус – доку­ментация о том, как употреблять их в контексте XSLT, скудная. Поэтому имеет смысл отметить несколько моментов. Большая часть этой информации имеется в документах о расширении Xalan (http://xml.apache.org/xalan-j/extensions.html), но ее легко не заметить, когда вы торопитесь сделать что-то к сроку.

Во-первых, сценарные расширения поддерживает только версия Xalan-Java, но не Xalan-C++.

Во-вторых, не забудьте добавить пути к файлам bsf.jar и js.jar (для JavaScript) в список путей, по которым ищутся классы. Это можно сделать в командной строке Unix:

java -cp /xalan/bin/xalan.jar:/xalan/bin/xercesImpl.jar:/xalan/bin/ bsf.jar: /xalan/

bin/js.jar org.apache.xalan.xslt.Process -in input.xml -xsl trans.xslt

или в переменной окружения CLASSPATH:

export CLASSPATH=/xalan/bin/xalan.jar:/xalan/bin/xercesImpl.jar:/ xalan/bin/bsf.jar:/xalan/bin/js.jar

На платформе Windows вместо разделителя-запятой употребляется точка с запятой, а вместо export – set.

В-третьих, обратите внимание, что файл js.jar не входит в дистрибутив Xalan. Его нужно отдельно скачать с сайта Mozilla.org (http://www.mozilla.org/rhino/).

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

Реализовывать функции расширения гораздо проще, чем элементы, поэтому примеров, приведенных в разделе «Решение» (вкупе с документацией по Xalan), должно быть достаточно. Далее мы будем говорить только об элементах расширения.

Когда вызывается функция, ассоциированная с элементом расширения, ей автоматически передаются два объекта. Первый – это контекст типа org.apache.xalan.extensions.XSLProcessorContext. С его помощью можно добраться до еще нескольких полезных объектов, например, до контек­стного узла, объекта Stylesheet и объекта-преобразователя. В нем же реализован метод outputToResultTree(Stylesheet stylesheetTree, java.lang.Object obj), который выводит данные в результирующее дерево. Тот факт, что эти объекты, написанные на Java, доступны из JavaScript, – заслуга каркаса Bean Scripting Framework (http://jakarta.apache.org/bsf/), код которого находится в файле bsf.jar.

Второй объект – это экземпляр класса org.apache.xalan.templates. ElemExtensionCall. Он представляет сам элемент расширения. Вы можете получить его атрибуты и дочерние элементы, необходимые сценарию для реа­лизации функциональности расширения. Делается это с помощью стандарт­ных функций работы с DOM, например: getAttribute(), getFirstChild(), getLastChild() и т.д.

Существует не так уж много ограничений на то, что можно делать с помо­щью сценарного элемента. Но, чтобы понять, как добиться желаемого резуль­тата, придется покопаться в исходном коде Xalan-Java и в документации. А во­обще-то писать расширения на сценарных языках имеет смысл только для простых задач, поскольку работают они значительно медленнее кода, написан­ного на Java.

См. также

Авторитетным источником информации о расширяемости Xalan служит до­кумент http://xml.apache.org/xalan-j/extensionslib.html.

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

По теме:

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