閑話休題: 比較演算子

スタイルシートでは必ず比較演算子を使用すると思いますが、みなさんはどの比較演算子をお使いでしょうか?
 
一般比較演算子: "=", "!=", ">", "<", "<=", ">="
値比較演算子  : "eq", "ne", "gt", "lt", "le", "ge"
 
XSLT1.0からXSLT2.0へスタイルシートを移行することがありますが、これらの演算子はついついXSLT1.0で使用した一般比較演算子のまま残してしまいます.私はなるべく値比較演算子に書き換えるようににしています.
理由ですが、本来スタイルシートで行うほとんどの比較は値比較のはずだからです.一般比較演算子をつかう必要があるのはのは、相当意識的にやる場合です.一般比較で右辺・左辺どちらかの式がシーケンスになってしまう場合、値比較演算子の場合エラーをだしてくれます.ノードを参照している場合ついついこういうことが起こりがちだからです.例えば
 
<xsl:if test="100 &gt; /root/data/@value">
 
ような場合、count(/root/data/) eq 1 ならばなんの問題もありませんが、間違えてcount(/root/data/) gt 1 の場合、すべての/root/dataに@valueがあれば、整数値1とシーケンスの比較になってしまいます.
 
<xsl:if test="100 gt /root/data/@value">
 
なら値比較演算子の右辺がシーケンスであるとエラーを出してくれるのですぐバグがわかります.
 
XPTY0004: A sequence of more than one item is not allowed as the second operand of 'gt'
Transformation failed: Run-time errors were reported
 
あまりご利益はないでしょうか?
それでは一般比較演算子のちょっと恐ろしい例です.様々なatomic valueとnodeの比較です.
 
[入力XML]
<?xml version="1.0" encoding="UTF-8" ?>
<root>
  <data>65536</data>
  <data>1254.25</data>
  <data>2.5e-10</data>
  <data>Monday</data>
  <data> </data>
</root>
 
[スタイルシート]
<xsl:variable name="data" select="/root/data" as="node()*"/>
<xsl:if test="65536 = $data">
  <xsl:message select="'65536 = $data'"/>
</xsl:if>
<xsl:if test="1254.25 = $data">
  <xsl:message select="'1254.25 = $data'"/>
</xsl:if>
<xsl:if test="2.5e-10 = $data">
  <xsl:message select="'2.5e-10 = $data'"/>
</xsl:if>
<xsl:if test="'Monday' = $data">
  <xsl:message select="'''Monday'' = $data'"/>
</xsl:if>
<xsl:if test="' ' = $data">
  <xsl:message select="''' '' = $data'"/>
</xsl:if>
 
結果は順当に以下のように表示されます.(幸せな世界です)
 
65536 = $data
1254.25 = $data
2.5e-10 = $data
'Monday' = $data
' ' = $data
 
ところが入力XMLの先頭行を次のように変えると一発でエラーになります.
<root>
  <data>Sunday</data>
  <data>65536</data>
  ...
 
FORG0001: ValidationException: Cannot convert string "Sunday" to a double
Transformation failed: Run-time errors were reported
 
"Sunday"はxs:doubleに変換できないというエラーです.これはatomic valueとnode()*との比較では、atomic valueにあわせてnode()*の値が変換して比較されるからです.Saxonの場合、右辺のシーケンスの$dataはシーケンスのメンバーが順に変換されてゆくようです.つまり$dataのメンバーを全部左辺のatomic valueに変換してから比較をするのではない.最初のXMLでRuntime errorが出なかったのは、比較順がうまくエラーがでないようになっていただけのことでした.
 
それでは入力XMLはこのままで、スタイルシートを次のように変えてみます.
 
[スタイルシート]
<xsl:if test="$data[2] = $data">
  <xsl:message select="string($data[2]), '= $data'"/>
</xsl:if>
<xsl:if test="$data[3] = $data">
  <xsl:message select="string($data[3]), '= $data'"/>
</xsl:if>
<xsl:if test="$data[4] = $data">
  <xsl:message select="string($data[4]),'= $data'"/>
</xsl:if>
<xsl:if test="$data[5] = $data">
  <xsl:message select="string($data[5]),' = $data'"/>
</xsl:if>
<xsl:if test="$data[6] = $data">
  <xsl:message select="concat('''',string($data[6]),''' = $data')"/>
</xsl:if>
 
すると、さっきはエラーが出たのに今度は以下のように表示されます.
 
65536 = $data
1254.25 = $data
2.5e-10 = $data
Monday  = $data
' ' = $data
 
何故エラーが出ないのでしょう?今度はnodeとnode()*との比較になります.この場合、XML Schemaでデータの型がマッピングされていない限り、xs:stringとxs:string*の比較になります.ですから決してRuntime errorは発生せず、かつ必ずヒットすることになります.
 
一般比較演算子と値比較演算子、特に一般比較演算子は注意した使い分けが必要でしょう.