XSLT 2.0で便利になった機能(48) node()*とdocument-node()

XSLT2.0でシーケンスが導入されて大変便利になりました.変数をxs:integer*、xs:string+などと定義できるとプログラミングの自由度が格段に向上します.さてそれではnode()*はどうでしょうか?これもまた便利なのですが、document-node()とどう違うのか?たまに混乱することがあります.実は先週node()*で定義していた変数がどうしてもうまく動いてくれなくて、結果的にdocument-node()が正解であることに気がつき、スタイルシートを全部修正しました.これに懲りたので、今回両者の違いについてまとめてみたいと思います.
 
まずdocument-nodeですが、変数の下にリテラルで要素を並べるとこのタイプになります.
 
<!--例1-->
<xsl:variable name="div">
  <span>りんご</span>
  <span>みかん</span>
  <span>バナナ</span>
</xsl:variable>
 
<xsl:template match="/">
  <xsl:message select="'$div/span[1]=',$div/span[1]"/>
</xsl:template>
 
出力は
 
$div/span[1]=<span>りんご</span>
 
これは次のように書いたことと同じになります.
 
<!--例2-->
<xsl:variable name="div" as="document-node()">
  <xsl:document>
    <span>りんご</span>
    <span>みかん</span>
    <span>バナナ</span>
  </xsl:document>
</xsl:variable>
 
では、
 
<!--例3-->
<xsl:variable name="div" as="node()*">
  <span>りんご</span>
  <span>みかん</span>
  <span>バナナ</span>
</xsl:variable>
 
と書いたらどうなるのでしょう?
 
$div/span[1]=
 
あれっ?何も出てきません.これはつまり、node()*に"/"の記法を適用しようとしてもダメなことを意味しています.この場合、代わりに、
 
<xsl:message select="'$div[1]=',$div[1]"/>
 
と書くと
 
$div[1]=<span>りんご</span>
 
と出てきてくれます.
 
あと重要なのがノードの相互関係です.例1と2では、
 
<xsl:message select="'$div/span[1]/following-sibling::node()[1]=',$div/span[1]/following-sibling::node()[1]"/>
 
 
$div/span[1]/following-sibling::node()[1]=<span>みかん</span>
 
で期待通りです.しかし例3で
 
<xsl:message select="'$div[1]/following-sibling::node()[1]=',$div[1]/following-sibling::node()[1]"/>
 
とやっても
 
$div[1]/following-sibling::node()[1]=
 
と何もでてきてくれません.そうです、次のように書くと何かしらツリーを作ってくれるように見えますが、
 
<!--例3-->
<xsl:variable name="div" as="node()*">
  <span>りんご</span>
  <span>みかん</span>
  <span>バナナ</span>
</xsl:variable>
 
これがnode()*として扱われると<span>りんご</span>、<span>みかん</span>、<span>バナナ</span>が各々div[1]、div[2]、div[3]になるだけで要素の兄弟関係はなくなるのです.
しかし入力XML
 
<?xml version="1.0" encoding="UTF-8" ?>
<div>
  <span>りんご</span>
  <span>みかん</span>
  <span>バナナ</span>
</div>
 
となっていて、
 
<xsl:variable name="div" as="node()*">
  <xsl:sequence select="/div/span[1]"/>
</xsl:variable>
 
とやれば、
 
<xsl:message select="'$div[1]/following-sibling::*[1]=',$div[1]/following-sibling::*[1]"/>
 
$div[1]/following-sibling::*[1]=<span>みかん</span>
 
と出ます.つまりソースXMLのノードを持って来れば、ノードの兄弟関係は保持されるのです.
 
ここで<xsl:sequence select="/div/span[1]"/>と、元の要素(ノード)を持ってきている点に注意してください.
 
<xsl:copy-of select="/div/span[1]"/>
 
とやると、要素がコピーされて新しい要素が作られるので、元の兄弟間の関係は保持されません.
 
あとnode()*の変数$aは、empty() eq true()になりえます.
 
<xsl:variable name="div" as="node()*">
</xsl:variable>
 
しかし、
 
<xsl:variable name="div" as="document-node()">
  <xsl:document/>
</xsl:variable>
 
は必ずexists($div) eq trueになります.(あたりまえですが...)
 
例えば次のようにテンポラリツリーを作って
 
<xsl:variable name="div" as="document-node()">
  <xsl:document>
    <xsl:apply-templates select="/div/span"/>
  </xsl:document>
</xsl:variable>
 
有効な要素(ノード)ができているかは
 
exists($div/*)
 
で確認できます.
 
node()*もdocument-node()も、複雑なデータ構造を扱うXSLTプログラミングでは必須になります.お使いになる場合、上記に述べた点に注意していただければと思います.