🔤

xsl:value-ofは値を返さない

2021/08/03に公開

xsl:value-ofは値を返す?

XSLTのxsl:value-ofは「値を返す」ものだと思ってないでしょうか?

(ここでは、「値」とはatomic valueのこととします。つまり、数値や文字列のような、個性のないデータのこととします。)

おそらくxsl:value-ofという名前に引きずられた理解なのでしょう。次のXSLTとCのコードを、ほぼ等価のように考えている人が多いようです。

XSLT

<xsl:function name="my:increment">
  <xsl:param name="n" />

  <xsl:value-of select="$n + 1" />
</xsl:function>

C

int myIncrement(
  int n
  )
{
  return n + 1;
}

値を返しているようにも見える

たしかに、xsl:value-ofは値を返しているようにも見えます。

たとえば次のサンプルXSLTを実行すると、2 + 1の結果、3と表示されます。

xsl:value-ofで数値を返しているつもりの例
<xsl:stylesheet version="3.0"
                xmlns:my="http://www.example.com/my"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output omit-xml-declaration="true" />

  <!-- 数値を返しているつもり -->
  <xsl:function name="my:increment">
    <xsl:param name="n" />

    <xsl:value-of select="$n + 1" />
  </xsl:function>

  <xsl:template name="xsl:initial-template">
    <xsl:message select="my:increment(2)" />
  </xsl:template>

</xsl:stylesheet>

実行結果

PS C:\value-of> java -jar saxon-he-10.5.jar -it -xsl:sample-1.xsl
3

xsl:value-ofは値を返さない

実際は、xsl:value-of値を返しません。返すのは、数値(xs:integer)でもないし、文字列(xs:string)でもないし、そもそもatomic valueですらありません。

それは、次のサンプルで確認できます。

xsl:value-ofの返す「値」の種類を確認する例
<xsl:stylesheet version="3.0"
                xmlns:my="http://www.example.com/my"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output omit-xml-declaration="true" />

  <!-- 数値を返しているつもり -->
  <xsl:function name="my:increment">
    <xsl:param name="n" />

    <xsl:value-of select="$n + 1" />
  </xsl:function>

  <xsl:template name="xsl:initial-template">
    <xsl:message select="'integer?', my:increment(2) instance of xs:integer" />
    <xsl:message select="'string?',  my:increment(2) instance of xs:string" />
    <xsl:message select="'atomic?',  my:increment(2) instance of xs:anyAtomicType" />
  </xsl:template>

</xsl:stylesheet>

実行結果

PS C:\value-of> java -jar saxon-he-10.5.jar -it -xsl:sample-2.xsl
integer? false
string? false
atomic? false

xsl:value-ofはテキスト ノードを新規作成する

xsl:value-ofは値を返すための命令ではなく、テキスト ノードを新規作成する命令です。

確認してみましょう。

xsl:value-ofがテキスト ノードの新規作成であることを確認する例
<xsl:stylesheet version="3.0"
                xmlns:my="http://www.example.com/my"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output omit-xml-declaration="true" />

  <!-- 数値を返しているつもり -->
  <xsl:function name="my:increment">
    <xsl:param name="n" />

    <xsl:value-of select="$n + 1" />
  </xsl:function>

  <xsl:template name="xsl:initial-template">
    <xsl:message select="'text?', my:increment(2) instance of text()" />
    <xsl:message select="'id',    my:increment(2) => generate-id()" />
    <xsl:message select="'id',    my:increment(2) => generate-id()" />
    <xsl:message select="'id',    my:increment(2) => generate-id()" />
  </xsl:template>

</xsl:stylesheet>

実行結果

PS C:\value-of> java -jar saxon-he-10.5.jar -it -xsl:sample-3.xsl
text? true
id Q1227264471
id Q1733581655
id Q1814827909

つまり、

<xsl:value-of select="$n + 1" />

は、

<xsl:text expand-text="yes">{$n + 1}</xsl:text>

と同じです。$n + 1の結果の値を返すのではありません。$n + 1の結果をもとに、毎回、新たな個性を持ったテキスト ノードを作成しているのです。

なお、今回の例で$n + 1の結果を数値(xs:integer)のまま返すには、xsl:value-ofではなくxsl:sequenceを使います。

<xsl:sequence select="$n + 1" />

@asを使おう

xsl:value-ofに限らず、こうした型にまつわる勘違いを避けるには、まずは@asの指定を徹底することです。

たとえば、最初のサンプルXSLTのxsl:functionas="xs:integer"を付ければ、プロセッサーがWarningを出してくれます。(Saxonの場合)

xsl:functionにasを付けた例
<xsl:stylesheet version="3.0"
                xmlns:my="http://www.example.com/my"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output omit-xml-declaration="true" />

  <!-- 整数を受け、整数を返す…つもりなので、as="xs:integer"を付けた -->
  <xsl:function name="my:increment"
                as="xs:integer">
    <xsl:param name="n"
               as="xs:integer" />

    <xsl:value-of select="$n + 1" />
  </xsl:function>

  <!-- 何も返さないテンプレートなので、as="empty-sequence()"を付けた -->
  <xsl:template name="xsl:initial-template"
                as="empty-sequence()">
    <xsl:message select="my:increment(2)" />
  </xsl:template>

</xsl:stylesheet>

実行結果

PS C:\value-of> java -jar saxon-he-10.5.jar -it -xsl:sample-5.xsl
Warning at function my:increment on line 10 of sample-5.xsl:
  SXWN9000  A function that computes atomic values should use xsl:sequence rather than xsl:value-of
3

@asを指定すれば、xsl:templatexsl:functionxsl:variableなどの意図が明確になるので、コード レビューも楽になります。

Discussion