#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
, чтобы гарантировать, что axsl:variable
будет числом, а не текстом? Кромеnumber()
обтекания переменной каждый раз, когда я ее вызываю? Мой фактический вариант использования имеет нечто более сложное, что я не могу вписать в@select
выражение. Я мог бы прибегнуть к использованию двухxsl:variable
s, одного со сложным созданием, а второгоnumber()
, который обтекает значение первого. Но было бы чище, если бы я мог просто сделать это с помощью одного.2. @alex.jordan Тип данных переменной, определенной с использованием шаблона, всегда будет фрагментом результирующего дерева — см.: w3.org/TR/1999/REC-xslt-19991116/#variable-values .