Туннельные параметры XSLT 2.0

Содержание

Работа с параметрами в XSLT 1.0 имеет некоторые недостатки. Во-первых, вы можете игнорировать «сигнатуру» вызываемого шаблона. При вызове можно передавать нужное количество параметров, передавать вдвое больше параметров или не передавать их вовсе. Вероятно, процессор XSLT 1.0 сделает то, что вы хотели, но если параметр задан неверно, найти причины возникших проблем будет нелегко. XSLT 2.0 требует, чтобы при вызове передавалось ровно столько параметров, сколько требуется; это делает программный код более логичным.

Вторая проблема возникает тогда, когда передаваемый параметр может быть использован другим шаблоном. Допустим, у нас имеется таблица стилей, генерирующая код HTML на базе DocBook. Документ DocBook содержит сотни элементов, поэтому мы ограничимся шаблонами для небольшого подмножества элементов DocBook. Мы возьмем документ DocBook и создадим два файла HTML. Каждый файл HTML содержит заголовки основных разделов (sect1/title) и все листинги из исходного документа (элементы DocBook <programlisting>). Преобразование выполняется дважды: в одном документе генерируется текст обычного размера, а во втором – более крупный текст.

Вот как выглядит таблица стилей без туннельных параметров:

<?xml version="1.0" encoding="utf-8"?>
<!-- normal_parameters.xsl -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html"/>

    <xsl:template match="/">
        <xsl:result-document href="regular-type.html" method="html">
            <html>
                <xsl:apply-templates select="*|text()">
                    <xsl:with-param name="code-font-size" select="'14'"/>
                </xsl:apply-templates>
            </html>
        </xsl:result-document>
        <xsl:result-document href="larger-type.html" method="html">
            <html>
                <xsl:apply-templates select="*|text()">
                    <xsl:with-param name="code-font-size" select="'20'"/>
                </xsl:apply-templates>
            </html>
        </xsl:result-document>
    </xsl:template>

    <xsl:template match="chapter">
        <xsl:param name="code-font-size"/>
        <head>
            <title><xsl:value-of select="title"/></title>
        </head>
        <body>
            <xsl:apply-templates select="*[not(name() = 'title')]|text()">
                <xsl:with-param name="code-font-size" select="$code-font-size"/>
            </xsl:apply-templates>
        </body>
    </xsl:template>

    <xsl:template match="programlisting">
        <xsl:param name="code-font-size"/>
        <pre>
            <span>
                <xsl:attribute name="style">
                    <xsl:text>font-family:monospace; font-size:</xsl:text>
                    <xsl:value-of select="$code-font-size"/>
                    <xsl:text>;</xsl:text>
                </xsl:attribute>
                <xsl:apply-templates select="*|text()">
                    <xsl:with-param name="code-font-size" select="$code-font-size"/>>
                </xsl:apply-templates>
            </span>
        </pre>
    </xsl:template>

    <xsl:template match="sect1/title">
        <xsl:param name="code-font-size"/>
        <h1>
            <xsl:apply-templates select="*|text()">
                <xsl:with-param name="code-font-size" select="$code-font-size"/>
            </xsl:apply-templates>
        </h1>
    </xsl:template>

    <!-- В реальной таблице стилей здесь следуют десятки шаблонов... -->

    <xsl:template match="*">
        <xsl:param name="code-font-size"/>
        <xsl:apply-templates select="*">
            <xsl:with-param name="code-font-size" select="$code-font-size"/>
        </xsl:apply-templates>
    </xsl:template>

</xsl:stylesheet>

Таблица стилей генерирует нужные результаты и создает документы regular-type.html и larger-type.html. Фрагмент файла regular-type.html может выглядеть так:

<h1>Goals of This Chapter</h1>
<h1>Branching Elements of XSLT</h1><pre>
<span style="font-family:monospace; font-size:14;">
&lt;xsl:if test="count(zone) &gt; 2"&gt;
&lt;xsl:text&gt;Applicable zones: &lt;/xsl:text&gt;
&lt;xsl:apply-templates select="zone"/&gt;
&lt;/xsl:if&gt;</span></pre><pre>
<span style="font-family:monospace; font-size:14;">
&lt;xsl:template match="table-row"&gt;
&lt;tr&gt;
&lt;xsl:attribute name="bgcolor"&gt;
&lt;xsl:choose&gt;

Файл larger-type.html идентичен за одним исключением: атрибут style содержит font-size:20; вместо font-size:14;. Однако таблица стилей получается громоздкой, а ее сопровождение потребует больших усилий, чем нам хотелось бы. Очень многие шаблоны имеют следующую структуру:

<xsl:template match="whatever">
    <xsl:param name="code-font-size"/>
    <!-- Что-то сделать с текущим элементом, -->
    <!-- затем обработать его потомков -->
    <xsl:apply-templates select="*|text()">
        <!-- Передать параметр code-font-size parameter -->
        <!-- на случай, если он понадобится позднее. -->
        <xsl:with-param name="code-font-size" select="$code-font-size"/>
    </xsl:apply-templates>
</xsl:template>

Проблема в том, что при каждом использовании <xsl:apply-templates> нам приходится передавать переменную code-font-size – просто на случай, если она понадобится шаблону, расположенному где-то дальше в цепочке. Любой из элементов, для которых мы написали шаблоны, может иметь потомка <programlisting>, так что выбора у нас нет. Ситуация усугубляется тем, что при каждом добавлении нового шаблона в таблицу стилей приходится добавлять ту же разметку [<xsl:param>](/xslt/xsl-param/) и <xsl:with-param>. Если вдруг появятся еще три параметра, которые придется передавать подобным способом, таблица стилей окончательно запутается, а любое изменение в ней приведет к возникновению ошибок (если вы забудете повторить внесенные изменения во всех шаблонах, которые от них зависят).

В подобных ситуациях на помощь приходят туннельные параметры. Туннельные параметры напоминают переменные с динамической областью видимости в функциональных языках программирования, таких как Haskell и Scheme. В этих языках переменная может входить в область видимости и выходить из нее, когда одна функция вызывает другую. В XSLT 2.0 туннельный параметр передается каждому шаблону, вызываемому явно или косвенно. Если один шаблон вызывает другой шаблон в ходе своего выполнения, то любой шаблон может использовать туннельный параметр, ссылаясь на него соответствующим образом (как на туннельный параметр). Вот как выглядит таблица стилей с туннельными параметрами:

<?xml version="1.0" encoding="utf-8"?>
<!-- tunnel_parameters.xsl -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html"/>

    <xsl:template match="/">
        <xsl:result-document href="regular-type.html" method="html">
            <html>
                <xsl:apply-templates select="*|text()">
                    <xsl:with-param name="code-font-size" select="'14'" tunnel="yes"/>
                </xsl:apply-templates>
            </html>
        </xsl:result-document>
        <xsl:result-document href="larger-type.html" method="html">
            <html>
                <xsl:apply-templates select="*|text()">
                    <xsl:with-param name="code-font-size" select="'20'" tunnel="yes"/>
                </xsl:apply-templates>
            </html>
        </xsl:result-document>
    </xsl:template>

    <xsl:template match="chapter">
        <head>
            <title><xsl:value-of select="title"/></title>
        </head>
        <body>
            <xsl:apply-templates select="*[not(name() = 'title')]|text()"/>
        </body>
    </xsl:template>

    <xsl:template match="programlisting">
        <xsl:param name="code-font-size" tunnel="yes"/>
        <pre>
            <span>
                <xsl:attribute name="style">
                    <xsl:text>font-family:monospace; font-size:</xsl:text>
                    <xsl:value-of select="$code-font-size"/>
                    <xsl:text>;</xsl:text>
                </xsl:attribute>
                <xsl:apply-templates select="*|text()"/>
            </span>
        </pre>
    </xsl:template>

    <xsl:template match="sect1/title">
        <h1>
            <xsl:apply-templates select="*|text()"/>
        </h1>
    </xsl:template>

    <!-- В реальной таблице стилей здесь следуют десятки шаблонов... -->

    <xsl:template match="*">
        <xsl:apply-templates select="*"/>
    </xsl:template>
</xsl:stylesheet>

Обратите внимание, насколько туннельные параметры упростили код. В корневом элементе туннельный параметр передается при запросе к процессору XSLT на преобразование всех элементов-потомков. Каждый раз, когда последующий шаблон вызывает другой шаблон посредством <apply-templates> или <call-template>, туннельные параметры незаметно передаются вызываемому шаблону. В таблице стилей этот параметр используется в единственном месте, где он действительно необходим: в шаблоне элемента programlisting.

Пара замечаний по поводу синтаксиса: прежде всего, у туннельных параметров элемент <xsl:with-param>, в котором параметр объявляется, должен содержать атрибут tunnel="yes". Во-вторых, шаблон, который хочет использовать туннельный параметр, должен содержать атрибут tunnel="yes" в элементе <xsl:param>, определяющем параметр. Если определение параметра не содержит атрибута tunnel="yes", процессор XSLT считает, что параметр является новой переменной, локальной для данного шаблона.

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

Вторая проблема заключается в том, что значение глобального параметра нельзя изменять. В нашем примере придется создавать две глобальные переменные: по одной для каждого значения, используемого в таблице стилей. Туннельный параметр позволяет задавать значение code-font-size каждый раз, когда потребуется обработать исходный документ. Туннельные параметры упрощают написание, отладку и сопровождение таблиц стилей.



19 июня 2015 г.