Главная » XSLT » Создание обобщенных функций отображения

0

Задача

Требуется создать повторно используемые шаблоны для выполнения опера­ций над элементами набора узлов.

Решение

В этом решении мы рекурсивно обрабатываем элементы, входящие в набор $nodes, вызывая для каждого из них обобщенную функцию $func. Эта функ­ция может иметь параметр $func-param. Значение $func-param по умолча­нию мы получаем из атрибута @param1 метки обобщенной функции. Благодаря этому соглашению значение по умолчанию может зависеть от обобщенной функции:

<xsl:template name="generic:map">

<xsl:param name="nodes" select="/.."/> <xsl:param name="func" select=" ‘identity’ "/> <xsl:param name="func-param1"

select="$generic:generics[self::generic:func and @name = $func]/@param1"/> <xsl:param name="i" select="1"/> <xsl:param name="result" select="/.."/>

<xsl:choose>

<xsl:when test="$nodes">

<xsl:variable name="temp"> <xsl:apply-templates

select="$generic:generics[self::generic:func and

@name = $func]"> <xsl:with-param name="x" select="$nodes[1]"/> <xsl:with-param name="i" select="$i"/>

<xsl:with-param name="param1" select="$func-param1"/> </xsl:apply-templates> </xsl:variable>

<xsl:call-template name="generic:map">

<xsl:with-param name="nodes" select="$nodes[position( ) > 1]"/> <xsl:with-param name="func" select="$func"/> <xsl:with-param name="func-param1" select="$func-param1"/> <xsl:with-param name="i" select="$i +1"/> <xsl:with-param name="result"

select="$result | exslt:node-set($temp)"/>

</xsl:call-template> </xsl:when> <xsl:otherwise>

<xsl:apply-templates select="$result" mode="generic:map"/> </xsl:otherwise> </xsl:choose> </xsl:template>

<xsl:template match="/ | node( ) | @*" mode="generic:map">

<node>

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

Как это происходит на практике, можно увидеть на примере обобщенной фун­кции incr:

<generic:func name="incr" param1="1"/> <xsl:template match="generic:func[@name=’incr’]"> <xsl:param name="x"/>

<xsl:param name="param1" select="@param1"/> <xsl:value-of select="$x + $param1"/> </xsl:template>

Параметр функции incr определяет величину приращения и по умолчанию равен 1. Ниже приведена таблица стилей, в которой incr применяется к набору узлов, состоящему из чисел. Сначала берется параметр по умолчанию, а потом ему присваивается значение 10. Заодно мы добавляем обобщенную функцию reciprocal и применяем ее для отображения множества чисел:

<xsl:stylesheet version="1.0"

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

xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic" xmlns:exslt="http://exslt.org/common"

extension-element-prefixes="exslt" exclude-result-prefixes="generic"> <xsl:import href="aggregation.xslt"/> <xsl:output method="xml" indent="yes"/> <!– Расширяем набор имеющихся обобщенных функций –>

<xsl:variable name="generic:generics" select="$generic:public-generics | document(”)/*/generic:*"/>

<!– Добавляем обобщенную функцию для вычисления обратной величины –> <generic:func name="reciprocal"/>

<xsl:template match="generic:func[@name=’reciprocal’]"> <xsl:param name="x"/> <xsl:value-of select="1 div $x"/> </xsl:template>

<!– Тестируем функциональность отображения –> <xsl:template match="numbers">

<results>

<incr>

<xsl:call-template name="generic:map"> <xsl:with-param name="nodes" select="number"/> <xsl:with-param name="func" select=" ‘incr’ "/> </xsl:call-template> </incr> <incr10>

<xsl:call-template name="generic:map">

<xsl:with-param name="nodes" select="number"/> <xsl:with-param name="func" select=" ‘incr’ "/> <xsl:with-param name="func-param1" select="10"/> </xsl:call-template> </incr10> <recip>

<xsl:call-template name="generic:map">

<xsl:with-param name="nodes" select="number"/> <xsl:with-param name="func" select=" ‘reciprocal’ </xsl:call-template> </recip> </results>

</xsl:template>

</xsl:stylesheet>

Ниже приведены результаты работы этой таблицы:

<incr>

<node>11</node>

<node>4.5</node>

<node>5.44</node>

<node>78.7777</node>

<node>-7</node>

<node>2</node>

<node>445</node>

<node>2.1234</node>

<node>8.77</node>

<node>4.1415927</node>

</incr> <incr10>

<node>2 0</node>

<node>13.5</node>

<node>14.440000000000001</node>

<node>87.7777</node>

<node>2</node>

<node>11</node> <node>454</node> <node>11.1234</node> <node>17.77</node> <node>13.1415927</node> </incr10> <recip>

<node>0.1</node>

<node>0.2 857142 857142 857</node>

<node>0.2 252 252 252 252 252</node>

<node>0.012 85 715 57142 9 857 2</node>

<node>-0.125</node>

<node>1</node>

<node>0.0 022 522 522 522 522 522</node> <node>0.8 901548 8 695032 94</node> <node>0.1287 001287 001287</node> <node>0.31830 98 814 8145367</node> </recip> </results>

Обсуждение

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

<generic:func name="less-than"/>

<xsl:template match="generic:func[@name=’less-than’]"> <xsl:param name="x"/> <!– верхняя граница –> <xsl:param name="param1"/>

<xsl:if test="$x &lt; $param1"><xsl:value-of select="$x"/></xsl:if> </xsl:template>

Затем полученные узлы запоминаются с помощью следующего фильтрующе­го шаблона:

<xsl:template match="/ | node( ) | @*" mode="generic:map"> <xsl:if test="string(.)"> <node>

<xsl:copy-of select="."/> </node> </xsl:if> </xsl:template>

Вот как эта техника применяется на практике:

<xsl:stylesheet version="1.0"

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

xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic" xmlns:exslt="http://exslt.org/common"

extension-element-prefixes="exslt" exclude-result-prefixes="generic">

<xsl:import href="aggregation.xslt"/>

<xsl:output method="xml" indent="yes"/>

<!– Тестируем функциональность отображения –> <xsl:template match="numbers">

<results>

<less-than-5>

<xsl:call-template name="generic:map">

<xsl:with-param name="nodes" select="number"/> <xsl:with-param name="func" select=" ‘less-than’ "/> <xsl:with-param name="func-param1" select="5"/> </xsl:call-template> </less-than-5>

<greater-than-5>

<xsl:call-template name="generic:map">

<xsl:with-param name="nodes" select="number"/> <xsl:with-param name="func" select=" ‘greater-than’ "/> <xsl:with-param name="func-param1" select="5"/> </xsl:call-template> </greater-than-5>

</results>

</xsl:template>

<xsl:template match="/ | node( ) | @*" mode="generic:map"> <xsl:if test="string(.)">

<node>

<xsl:copy-of select="."/> </node> </xsl:if> </xsl:template>

Вот результат тестирования этой таблицы стилей:

<results>

<less-than-5>

<node>3.5</node> <node>4.44</node> <node>-8</node> <node>1</node> <node>1.1234</node> <node>3.1415927</node> </less-than-5> <greater-than-5> <node>10</node> <node>77.7777</node> <node>444</node> <node>7.77</node> </greater-than-5> </results>

Отображение можно применять не только для обработки чисел. Рассмотрим следующую таблицу стилей, которая вычисляет длины всех элементов para в до­кументе DocBook:

<xsl:stylesheet version="1.0"

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

xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic" xmlns:exslt="http://exslt.org/common"

extension-element-prefixes="exslt" exclude-result-prefixes="generic"> <xsl:import href="aggregation.xslt"/> <xsl:output method="xml" indent="yes"/> <!– Расширяем набор имеющихся обобщенных функций –>

<xsl:variable name="generic:generics" select="$generic:public-generics | document(”)/*/generic:*"/>

<!– Добавляем обобщенную функцию для вычисления длины –> <generic:func name="length"/>

<xsl:template match="generic:func[@name=’length’]"> <xsl:param name="x"/>

<xsl:value-of select="string-length($x)"/> </xsl:template>

<!– Тестируем функциональность отображения –> <xsl:template match="/">

<para-lengths>

<xsl:call-template name="generic:map">

<xsl:with-param name="nodes" select="//para"/> <xsl:with-param name="func" select=" ‘length’ "/> </xsl:call-template> </para-lengths>

</xsl:template>

<xsl:template match="/ | node( ) | @*" mode="generic:map"> <length>

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

</xsl:stylesheet>

Или такой пример, где создается реферат документа путем извлечения пер­вых трех предложений из каждого абзаца:

<xsl:stylesheet version="1.0"

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

xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic" xmlns:exslt="http://exslt.org/common"

extension-element-prefixes="exslt" exclude-result-prefixes="generic"> <xsl:import href="aggregation.xslt"/> <xsl:output method="xml" indent="yes"/>

<!– Расширяем набор имеющихся обобщенных функций –>

<xsl:variable name="generic:generics" select="$generic:public-generics | document(”)/*/generic:*"/>

<!– Обобщенная функция для извлечения предложений –> <generic:func name="extract-sentences" param1="1"/> <xsl:template match="generic:func[@name=’extract- sentences’]" name="generic:extract-sentences"> <xsl:param name="x"/>

<xsl:param name="param1" select="@param1"/> <xsl:choose>

<xsl:when test="$param1 >= 1 and contains($x,’.’)"> <xsl:value-of select="substring-before($x,’.’)"/> <xsl:text>.</xsl:text>

<xsl:call-template name="generic:extract-sentences">

<xsl:with-param name="x" select="substring-after($x,’.’)"/>

<xsl:with-param name="param1" select="$param1 – 1"/> </xsl:call-template> </xsl:when> <xsl:otherwise/> </xsl:choose> </xsl:template>

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

<xsl:call-template name="generic:map">

<xsl:with-param name="nodes" select="//para"/> <xsl:with-param name="func" select=" ‘extract-sentences’ "/> <xsl:with-param name="func-param1" select="3"/> </xsl:call-template> </summary>

</xsl:template>

<xsl:template match="/ | node( ) | @*" mode="generic:map"> <para>

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

</xsl:stylesheet>

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

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template name="extract-sentences"> <xsl:param name="text"/>

<xsl:param name="num-sentences" select="1"/> <xsl:choose>

<xsl:when test="$num-sentences >= 1 and contains($text,’.’)"> <xsl:value-of select="substring-before($text,’.’)"/> <xsl:text>.</xsl:text>

<xsl:call-template name="extract-sentences">

<xsl:with-param name="text" select="substring-after($text,’.’)"/> <xsl:with-param name="num-sentences" select="$num-sentences – 1"/>

</xsl:call-template> </xsl:when> <xsl:otherwise/> </xsl:choose> </xsl:template>

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

<xsl:apply-templates select=".//para"/> </summary> </xsl:template>

<xsl:template match="para"> <para>

<xsl:call-template name="extract-sentences"> <xsl:with-param name="text" select="."/> <xsl:with-param name="num-sentences" select="3"/> </xsl:call-template> </para> </xsl:template>

</xsl:stylesheet>

Однако, если в одной таблице стилей нужно выполнить несколько отображе­ний, то применение обобщенной реализации позволит уменьшить объем специа­лизированного кода.

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

<xsl:template name="generic:map2">

<xsl:param name="nodes1" select="/.."/> <xsl:param name="nodes2" select="/.."/> <xsl:param name="func" select=" ‘identity’ "/>

<xsl:param name="func-param1" select="$generic:generics[self::generic:func and @name = $func]/@param1"/>

<xsl:param name="i" select="1"/> <xsl:param name="result" select="/.."/>

<xsl:choose>

<xsl:when test="$nodes1 and $nodes2"> <xsl:variable name="temp"> <xsl:apply-templates

select="$generic:generics[self::generic:aggr-func and

@name = $func]"> <xsl:with-param name="x" select="$nodes1[1]"/> <xsl:with-param name="accum" select="$nodes2[1]"/> <xsl:with-param name="i" select="$i"/>

<xsl:with-param name="param1" select="$func-param1"/> </xsl:apply-templates> </xsl:variable>

<xsl:call-template name="generic:map2"> <xsl:with-param name="nodes1" select="$nodes1[position( ) > 1]"/> <xsl:with-param name="nodes2" select="$nodes2[position( ) > 1]"/> <xsl:with-param name="func" select="$func"/> <xsl:with-param name="func-param1" select="$func-param1"/> <xsl:with-param name="i" select="$i +1"/>

<xsl:with-param name="result" select="$result | exslt:node- set($temp)"/>

</xsl:call-template> </xsl:when> <xsl:otherwise>

<xsl:apply-templates select="$result" mode="generic:map"/> </xsl:otherwise> </xsl:choose> </xsl:template>

Как и в случае generic:map, полезность функции generic:map2 зависит от того, как часто она применяется.

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

По теме:

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