➰
XSLTのfor-eachはループしない(だから便利)
xsl:for-each
はループする?
XSLTのxsl:for-each
は「ループ」だと思ってないでしょうか?
おそらくC等のfor
に引きずられた理解なのでしょう。次のXSLTとCのコードを、ほぼ等価のように考えている人が多いようです。
XSLT
<xsl:for-each select="1 to 5">
<xsl:variable name="i" select="." />
<xsl:value-of select="$i " />
</xsl:for-each>
C
for (i = 1; i <= 5; i++) {
printf("%d ", i);
}
「上から下に処理して、また上に戻る(ループする)」というイメージですね。次の図のような。
ループしているようにも見える
たしかに、xsl:for-each
はループしているようにも見えます。
たとえば次のサンプルXSLTを実行すると、1秒間隔で$i=1
~$i=5
の順にメッセージが出力されます。
xsl:for-eachの例(要Saxon-EE)
<xsl:stylesheet exclude-result-prefixes="#all"
extension-element-prefixes="saxon"
version="3.0"
xmlns:saxon="http://saxon.sf.net/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"
omit-xml-declaration="true" />
<xsl:param name="THREADS"
as="xs:positiveInteger"
select="xs:positiveInteger(1)" />
<xsl:template name="xsl:initial-template"
as="element(out)">
<out>
<xsl:for-each select="1 to 5"
saxon:threads="{$THREADS}">
<xsl:variable name="i"
as="xs:integer"
select="." />
<xsl:message select="'$i=' || $i"
saxon:time="yes" />
<!-- Sleep for a second -->
<saxon:do action="Q{java:java.lang.Thread}sleep(1000)" />
<xsl:sequence select="$i" />
</xsl:for-each>
</out>
</xsl:template>
</xsl:stylesheet>
そうした挙動からも、xsl:for-each
が次のように「ループ」するものと感じられるのでしょう。
-
$i=1
を1秒間処理して - 上に戻って
-
$i=2
を1秒間処理して - 上に戻って
- …
$i=5
が終わるまでループ…全体の処理に約5秒かかる
xsl:for-each
はループしない
実際は、xsl:for-each
はループしません。
xsl:for-each
は「ループ」ではなく、次のように「処理を割り当てる」命令です。
-
@select
で指定したアイテム一つ一つに、同じ処理を割り当てる - 各アイテムに割り当てた処理は、他のアイテムとは関係なく、独立して実行する
- 処理の実行結果は、アイテムの順に出力する
イメージとしてはこう。
ループしないからこそ便利
xsl:for-each
がループしないのは、欠点ではなく長所です。
- 各アイテムの処理には順序関係がないので、並列処理できる
- それでいて、処理結果はアイテム順になる
- 各アイテムの処理は独立しているので、基本的には排他制御が不要
たとえば、先ほどと同じサンプルXSLTを、$THREADS
パラメーターに5
をセットして実行してみます。
-
$i=1
~$i=5
は、並列に処理される - 並列処理なので、メッセージの出力順はタイミングによってランダム
- 並列処理のおかげで、全体の処理は5秒ではなく約1秒で終わる
- それでいて、処理の結果はちゃんと
$i=1
~$i=5
の順になる(<out>1 2 3 4 5</out>
)
特に、I/O待ちが長い処理など複数実行するときは、xsl:for-each
でこうした並列化をしなければ、やってられません。
xsl:iterate
ループしたければこのように、xsl:for-each
がループしないのは長所なんですが、あたかも短所のように語られることがあります。たとえば次のような。
-
xsl:for-each
では、ループしながら変数を順に更新できない -
xsl:for-each
では、ループの途中で条件によってbreakすることができない
これらは、どれもxsl:for-each
を「ループ」と誤解しているにすぎません。ループしないので、「順」も「途中」も意味をなしません。
ループしたいときは、xsl:for-each
ではなく、xsl:iterate
の出番です。xsl:iterate
では、変数を順に更新したり、途中でbreakすることもできます。
Discussion