Jintrick.netagenda2002年06月アーカイブ → 2002年06月26日

カレントノードの強制変更(XSLT)

次のようなXMLを考える:

<?xml version="1.0"?>
<document>
    <h1>大見出し</h1>
    <p>文章。</p>
    <h2>中見出し1</h2>
    <p>文章。</p>
    <h3>小見出し1</h3>
    <p>文章。</p>
    <h2>中見出し2</h2>
    <p>文章。</p>
    <h3>小見出し2</h3>
    <p>文章。</p>
</document>

これは、document要素の子孫が全て兄弟になっている(フラットな)構造である。

ここで、document要素を変換するテンプレート内で、h2要素の数だけ繰り返し処理を行い(for-each要素)、そのfor-each要素内において:

  1. 現在処理を行っているh2要素は、何番目の兄弟か
  2. 次に出現するh2要素は、何番目の兄弟か

の2つを、このfor-each要素内で扱えるローカル変数($H2posおよび$nextH2pos)にそれぞれバインドするにはどうすれば良いか。

一応はできたが、もう、悪い夢のようだ。

<xsl:template match="document">
    <xsl:for-each select="h2">
        <xsl:vriable name="H2pos">
            <xsl:number level="single" count="*" />
        </xsl:variable>
        <xsl:variable name="nextH2pos">
            <xsl:for-each select="following-sibling::h2[1]">
                <xsl:number level="single" count="*" />
            </xsl:for-each>
        </xsl:variable>
        以下テンプレート
    </xsl:for-each>
</xsl:template>

for-each要素直下においたvariable要素は、そのfor-each要素内でのみ有効なローカル変数になる。ところが、カレントノードを変更しないことには、number要素による「カレントノード以外のノード」のカウントができず、for-each要素内でカレントノードを変更するには、その中でもう一度for-each要素を作るしかない。つまり知る限り、template要素内でカレントノードを変更するには、for-each要素を使うしかないということ。for-eachは、指定したノードリストで繰り返しを行う為にあるのだが、ノードリストではなく特定のノードをselect属性に指定し、また、そのfor-each要素をvariable要素の子にすることで、ローカル変数が子のfor-each要素内に隠蔽されてしまうのを防ぐ。小汚いハックであるとしか思えないよ。

というわけで、この例では、<xsl:for-each select="h2"></xsl:for-each>内の式なら何処でも、$H2pos、$nextH2posを参照できる。

実際には「最後のh2要素」という例外を処理する為にwhen、otherwise要素を使って更に分岐しなければならないが、これでようやく内容と構造の分離を図ることができそうだ。簡単に言ってしまえば、フラットな文書にツリー構造を与えられる。問題は、h1からh6までをこの方法を使って実現しようとすると、ちと人間が読むようなものではなくなってしまうことだ。「冗長、ここに極まれり」


しかしどうも腑に落ちない。要素の位置を返すposition()関数が非力過ぎる。用途の違うnumber要素の方が使い勝手が良いというのは一体どういうことなのか。XPathで可能なら変数にバインドする必要もないので、コードがスッキリするのに。XSLTの要素、XPathの関数などの中で、何か重大なものを見落としているような気がしてならない。一応全部目を通したんだけどなあ。

教えてクンに変身2秒前?


webmaster@jintrick.net
公開: 2002年06月26日
カテゴリ: XSLT