Несоответствие XSLT тому, как используется переменная

#xslt

#xslt

Вопрос:

Мой вопрос касается xsl:variable и синтаксиса для предиката в Xpath. Я свел свой вопрос к тому, что этот короткий XML может помочь мне продемонстрировать:

 <root>
  <tabular>
    <col halign="left"/>
    <col halign="right"/>
    <row>
      <cell>Some content</cell>
      <cell>Some content</cell>
    </row>
  </tabular>
</root>
 

В моем приложении, когда я применяю шаблон к a cell , мне нужно получить доступ @halign к соответствующему col . При этом я столкнулся с несоответствием между выражениями Xpath, которые, по моему мнению, должны быть эквивалентными. Я хотел бы понять, почему это происходит. Чтобы продемонстрировать, я применяю XSL в конце этого сообщения, используя XSLT 1.0.

cell Шаблон в моем XSLT здесь глупый, но он описывает несоответствие, которое я не понимаю. В основном он неоднократно пытается напечатать @halign значение, соответствующее второй ячейке. Сначала используйте $col переменную, которая имеет значение 2 . Затем использование [position()=$col] . Затем использование [number($col)] . Затем просто используйте [2] , жестко запрограммированный. Наконец, использование отдельной $colsel переменной, которая была определена с использованием @select атрибута.

Я ожидаю увидеть:

 ancestor::tabular/col[...]/@halign
                [2]  makes  right
     [position()=2]  makes  right
        [number(2)]  makes  right
(hard 2)        [2]  makes  right
(var @select)   [2]  makes  right
 

но вместо этого я вижу:

 ancestor::tabular/col[...]/@halign
                [2]  makes  left
     [position()=2]  makes  right
        [number(2)]  makes  right
(hard 2)        [2]  makes  right
(var @select)   [2]  makes  right
 

Кто-нибудь может предложить объяснение того, почему использование [$col] ведет себя по-другому?

Вот XSL:

 <xsl:template match="/">
    <xsl:apply-templates select="root/tabular"/>
</xsl:template>

<xsl:template match="tabular">
  <xsl:apply-templates select="row"/>
</xsl:template>

<xsl:template match="row">
  <xsl:apply-templates select="cell"/>
</xsl:template>

<?xml version='1.0'?> <!-- As XML file -->

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="/">
    <xsl:apply-templates select="root/tabular"/>
</xsl:template>

<xsl:template match="tabular">
  <xsl:apply-templates select="row"/>
</xsl:template>

<xsl:template match="row">
  <xsl:apply-templates select="cell[2]"/>
</xsl:template>

<xsl:template match="cell[2]">
    <xsl:variable name="col">
        <xsl:value-of select="2"/>
    </xsl:variable>
    <xsl:variable name="colsel" select="2"/>
    <xsl:text>ancestor::tabular/col[...]/@halign</xsl:text>
    <xsl:text>amp;#xa;</xsl:text>
    <xsl:text>                [</xsl:text>
    <xsl:value-of select="$col"/>
    <xsl:text>]  makes  </xsl:text>
    <xsl:value-of select="ancestor::tabular/col[$col]/@halign"/>
    <xsl:text>amp;#xa;</xsl:text>
    <xsl:text>     [position()=</xsl:text>
    <xsl:value-of select="$col"/>
    <xsl:text>]  makes  </xsl:text>
    <xsl:value-of select="ancestor::tabular/col[position()=$col]/@halign"/>
    <xsl:text>amp;#xa;</xsl:text>
    <xsl:text>        [number(</xsl:text>
    <xsl:value-of select="$col"/>
    <xsl:text>)]  makes  </xsl:text>
    <xsl:value-of select="ancestor::tabular/col[number($col)]/@halign"/>
    <xsl:text>amp;#xa;</xsl:text>
    <xsl:text>(hard 2)        [2]  makes  </xsl:text>
    <xsl:value-of select="ancestor::tabular/col[2]/@halign"/>
    <xsl:text>amp;#xa;</xsl:text>
    <xsl:text>(var @select)   [</xsl:text>
    <xsl:value-of select="$colsel"/>
    <xsl:text>]  makes  </xsl:text>
    <xsl:value-of select="ancestor::tabular/col[$colsel]/@halign"/>
    <xsl:text>amp;#xa;</xsl:text>
    <xsl:text>amp;#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>
 

Ответ №1:

Давайте воспользуемся более удобным примером:

XML

 <root>
    <item>first</item>
    <item>second</item>
</root>
 

XSLT 1.0

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

<xsl:variable name="num" select="2"/>
<xsl:variable name="str" select="string(2)"/>
<xsl:variable name="rtf">2</xsl:variable>

<xsl:template match="/root">
    <results>
        <num>
            <xsl:copy-of select="item[$num]"/>
        </num>
        <str>
            <xsl:copy-of select="item[$str]"/>
        </str>
        <rtf>
            <xsl:copy-of select="item[$rtf]"/>
        </rtf>
    </results>
</xsl:template>

</xsl:stylesheet>
 

Результат

 <?xml version="1.0" encoding="UTF-8"?>
<results>
  <num>
    <item>second</item>
  </num>
  <str>
    <item>first</item>
    <item>second</item>
  </str>
  <rtf>
    <item>first</item>
    <item>second</item>
  </rtf>
</results>
 

Теперь вы спрашиваете, почему разница в результатах. Ответ можно найти в спецификации XPath, которая предписывает, как должен оцениваться предикат:

PredicateExpr вычисляется путем вычисления выражения и преобразования результата в логическое значение. Если результатом является число, результат будет преобразован в true, если число равно позиции контекста, и будет преобразован в false в противном случае; если результат не является числом, то результат будет преобразован, как при вызове булевой функции.

В первом случае значением $num переменной является число 2. Следовательно, результатом вычисления выражения внутри предиката является число, и предикат будет истинным, когда число равно позиции контекста, что верно только для item во второй позиции.

Во втором случае значением $str переменной является строка «2». Поэтому выражение внутри предиката не вычисляется до числа и будет преобразовано в логическое значение, выполнив:

 boolean("2") 
 

который возвращает true() для всех item s, независимо от их положения.

В третьем случае значение $rtf переменной представляет собой фрагмент дерева результатов, который содержит текстовый узел, состоящий из символа «2». При помещении в предикат результат будет аналогичен предыдущему экземпляру: результатом вычисления выражения не является число, и преобразование его в логическое значение приведет к значению true() . Обратите внимание, что ваш:

 <xsl:variable name="col">
    <xsl:value-of select="2"/>
</xsl:variable>
 

делает то же самое.


Обратите внимание также, что в XSLT 1.0 xsl:value-of инструкция возвращает значение первого узла в выбранном наборе узлов. Поэтому, если мы изменим наш шаблон на:

 <xsl:template match="/root">
    <results>
        <num>
            <xsl:value-of select="item[$num]"/>
        </num>
        <str>
            <xsl:value-of select="item[$str]"/>
        </str>
        <rtf>
            <xsl:value-of select="item[$rtf]"/>
        </rtf>
    </results>
</xsl:template>
 

результатом будет:

 <results>
  <num>second</num>
  <str>first</str>
  <rtf>first</rtf>
</results>
 

но все же оба item s выбираются постепенно item[$str] item[$rtf] .

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

1. Спасибо, я думаю, что мое основное непонимание заключается в том, как value-of возвращается строка. Я не знал об этом.

2. На самом деле он возвращает не строку, а текстовый узел. В динамически типизированном языке с большим количеством преобразований различия важны.

Ответ №2:

Измените объявление переменной на:

 <xsl:variable name="col" select="2"/>
 

и он будет вести себя так, как вы ожидаете, и выберите второе col .

Вы объявили переменную с помощью xsl:value-of : <xsl:value-of select="2"/> , которая создает вычисляемый text() узел.

Когда вы используете эту $col переменную саму по себе в a predicate , это строковое значение "2" вычисляется как true() в тесте предиката, а не если бы оно было a number() и затем интерпретировалось бы как сокращенное значение position() = 2 .

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

1. Спасибо. В XSL 1.0 есть ли какой-либо способ, когда он не используется @select , чтобы гарантировать, что a xsl:variable будет числом, а не текстом? Кроме number() обтекания переменной каждый раз, когда я ее вызываю? Мой фактический вариант использования имеет нечто более сложное, что я не могу вписать в @select выражение. Я мог бы прибегнуть к использованию двух xsl:variable s, одного со сложным созданием, а второго number() , который обтекает значение первого. Но было бы чище, если бы я мог просто сделать это с помощью одного.

2. @alex.jordan Тип данных переменной, определенной с использованием шаблона, всегда будет фрагментом результирующего дерева — см.: w3.org/TR/1999/REC-xslt-19991116/#variable-values .