先日レビューのために何年も前のコードを読み返す機会がありました.恥ずかしながら自分のコードを見ても理解できない箇所があります.それは、変数のSequenceType Syntaxにおける、OccurrenceIndicatorの使い方です.
SequenceType Syntax
OccurrenceIndicator
つまり文字列だと、xs:string、xs:styring?、xs:string*、xs:string+の"?"、"*"、"+"ですが、これをどのような目的で使い分けているかがいまいち判然としないのです.特にxs:string?の使い方が自分の書いたコードなのによくわかりません.
この際よい機会と思って調べなおしてみました.
xs:string?の使用箇所は主に以下のようなものでした.
(1)テンプレートのパラメータ定義
例えば以下のようなコードがあります.
<xsl:template name="getXXX" as="xs:string">
<xsl:param name="prmVarName" as="xs:string"/>
<xsl:param name="prmSubject" as="xs:string?" required="no" select="'デフォルト値'"/>
$prmSubjectはxs:string?で空シーケンスを許容します.ところがこのテンプレートgetXXXを呼び出しているどの箇所を見ても、$prmSubjectを指定して呼び出している箇所はありません.ですので$prmSubjectの値は'デフォルト値'になります.またテンプレート内でもempty($prmSubject)を意識的に判定している箇所はありません.そうすると事実上
<xsl:param name="prmSubject" as="xs:string?" required="no" select="'デフォルト値'"/>
は
<xsl:param name="prmSubject" as="xs:string" required="no" select="'デフォルト値'"/>
でもまったく問題ありません.どうもこの時点の私の理解は稚拙で
<!--① パラメータを渡さない(省略する)-->
<xsl:call-template name="getXXX">
<xsl:with-param name="prmVarName" select="'変数名'"/>
<!--xsl:with-param name="prmSubject" select="()"/-->
</xsl:call-template>
と
<!--② 空シーケンスを渡す-->
<xsl:call-template name="getXXX">
<xsl:with-param name="prmVarName" select="'変数名'"/>
<xsl:with-param name="prmSubject" select="()"/>
</xsl:call-template>
を混同していたようです.XSLTでは「パラメータを渡さない(省略する)」ことと「パラメータを空シーケンスで渡す」ことはまったく違います.②の
<xsl:with-param name="prmSubject" select="()"/>
とした場合、呼び出されたテンプレート側ではempty($prmSubject) eq true()が成立します.①の場合は、$prmSubject eq 'デフォルト値' となります.
(2) 変数で使用する場合
<xsl:variable name="subject" as="xs:string?" select="if (exists($prmElement/@subject)) then string($prmElement/@subject) else ()"/>
という使用方法がありました.何故
<xsl:variable name="subject" as="xs:string" select="string($prmElement/@subject)"/>
としていないかと言えば、属性のsubject=""と属性のsubjectが指定されていない場合を明確に識別する場合です.これは意味がありました.
(3)変数で使用する場合
<xsl:variable name="prmAnsIndex as="xs:integer" required="yes"/>
<xsl:variable name="ansType" as="xs:string?" select="('A','B','C','other')[$prmAnsIndex]"/>
のようなものはxs:string?を使用して正解です.例えば$prmAnsIndex eq 0の場合、empty($ansType) eq true()になります.
ただしxs:string?と宣言した変数のまたはパラメータの扱いには十分注意する必要があると思います.必ず空シーケンスかチェックすべきです.
例えば上記で空シーケンスかチェックせずに値比較演算子を適用して
<xsl:if test="$ansType eq ''">
とやるのは危険です.$ansTypeが空シーケンスの場合、この$ansType eq ''という比較そのものが空シーケンスを返します.つまり、empty($ansType eq '') eq true()が成立します.ところが一般比較演算子では
<xsl:if test="$ansType = ''">
は、false()となります.こういうややこしい話にはまらないように、
<xsl:choose>
<xsl:when test="empty($ansType)">
<!--空シーケンスの場合の処理-->
</xsl:when>
<xsl:otherwise>
<!--空シーケンスでない場合の処理-->
</xsl:otherwise>
</xsl:choose>
と明確に分岐すべきでしょう.
"?"にくらべて"*"や"+"は、まだ用途が明確です.
<xsl:variable name="level" as="xs:string+" select="('A','B','C','D')"/>
は、自分で必ず一個以上の文字列のシーケンスを生成することを意図してコードを書いています.
また、
<xsl:variable name="p" as="element()*" select="$section/p"/>
のようにして
<xsl:apply-templates select="$p"/>
と処理できます.$sectionの子要素に<p>があれば何らかの処理をし、なければ何もしません.入力の<section>要素に応じてcount($p)の値は変動します.このような場合、"*"は適役です.
このようなOccurrenceIndicatorの使い方を教えてくれる本というのはなかなか見当たりません.結局はプログラマーの裁量次第でどのようなコードでもかけて、しかもなんとなく動いてしまう場合があります.しかしいったん運用に乗ってしまうと、おいそれとはコードは変更できなくなります.自分の過去のコードをgrepしてみて今回はゾッとしました.xs:string?は、もし他の方がメンテナンスする事態になったら解読に困ることは目に見えています.この次からはこのような過ちは犯さないようにしたいと反省しています.