レベル: 中級 Doug Tidwell (dtidwell@us.ibm.com), Senior Programmer, IBM
2007年 9月 04日 XPath 2.0 と XSLT 2.0 の興味深い 3 つの新機能が、 item データ型と to 演算子、そしてシーケンスの概念です。これらの機能を利用して XML 文書の高度な HTML ビューを生成するサンプル・アプリケーションを作成しましょう。また XSLT 2.0 の新しい機能を利用して、維持管理しやすい簡潔なスタイルシートを作成しましょう。これらを作成する中で、XSLT 2.0 でのデータ型の扱いに少し時間をとり、さらに新しい <xsl:function> 要素の使い方を学びます。
XPath 2.0 と XSLT 2.0 の新しい重要な概念の 1 つは、すべてがシーケンスであることです。XPath 1.0 と XSLT 1.0 では、通常はノードのツリーを扱いました。構文解析された XML 文書は、文書ノードとその子孫を含むツリーでした。この、ノードのツリーを使うことで、ルート要素のすべての子孫と属性、そして兄弟と共に、ルート要素のノードを見つけることができました。(XML ファイルのルート要素の外にあるすべてのコメントや処理命令は、ルート要素の兄弟と見なされます。)
XPath 2.0 と XSLT 2.0 で XML 文書を扱う際には、XPath 1.0 と XSLT 1.0 でのツリー構造と同じように、シーケンスを使います。シーケンスは 1 つの項目 (文書ノード) を含み、この使い方はこれまでと同じです。しかし、今度はアトミック値のシーケンスを作成できるのです。リスト 1 は、後ほど示すサンプル・アプリケーションの一部です。このアプリケーションでは、16 チーム対抗の勝ち抜きトーナメント用のデータを処理しますが、これがアトミック値のシーケンスの例を示しています。
リスト 1. アトミック値のシーケンス
<xsl:variable name="seeds" as="xs:integer*">
<xsl:sequence
select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable> |
このコードは変数 $seeds を定義しています。新しい <xsl:sequence> 要素は、ご想像の通り、項目のシーケンスを定義します。この場合、項目は XML Schema、 xs:integers です。新しい as 属性はこの変数のデータ型を定義し、またアスタリスク ( xs:integer*) は、このシーケンスがゼロ以上の整数を含むことを意味します。XPath 1.0 と XSLT 1.0 では、16 個の異なるテキスト・ノードを作成し、そしてこれらのノードを変数にグループ分けします。XPath 2.0 と XSLT 2.0 では、シーケンスは 1 次元の数字配列として動作します。これは、まさにこのサンプル・アプリケーションが必要とするものです。
シーケンスはいくつかのルールに従います。第 1 に、他のシーケンスを含むことはできません。新規に 3 つの項目からなるシーケンスとそれに続く 3 つの項目のシーケンスを作成すると、その結果は 6 つの項目からなる新しい 1 つのシーケンスになります。第 2 に、シーケンスではノードと項目を混合させることができます。リスト 1 に示したアトミック値を含み、またすべての <contestant> 要素を含む、1 つのシーケンスを作成することができます (リスト 2)。
リスト 2. ノードとアトミック値のシーケンス
<xsl:variable name="seeds" as="item()*">
<xsl:for-each select="/bracket/contestants/contestant">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:sequence
select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable> |
変数 $seeds は、先ほど使用したすべての contestant ノードと 16 個のアトミック値を含んでいます。この変数のデータ型が item()* であることに注目してください。item はノードあるいはアトミック値なので、この変数はあらゆるものを含むことができます。
これでシーケンスと item の基本は理解できたので、XPath 2.0 と XSLT 2.0 の新機能、to 演算子を見てみましょう。to 演算子を利用すると、ある範囲の整数を選択することができます。例えば、リスト 3 のようなシーケンスを作成することができます。
リスト 3. to 演算子を使って作成した整数のシーケンス
<xsl:variable name="range" as="item()*">
<xsl:sequence select="1 to 16"/>
</xsl:variable> |
このコードは、1 から 16 までの整数を含む、$range という名前の変数を作成します。サンプル・アプリケーションでは、to 演算子をループ・メカニズムとして使うことができます (リスト 4)。
リスト 4. to 演算子をループに使う
<xsl:for-each select="1 to 32">
<!-- Do something useful here -->
</xsl:for-each> |
スタイルシートを作成する前に、サンプル・アプリケーションを少し詳しく見てみましょう。
サンプル・アプリケーションを理解する
この記事のサンプル・アプリケーションでは、16 チーム対抗の勝ち抜きトーナメント用のデータを処理します。ご想像の通り、トーナメントのデータは XML で表現されています。ここでは XSLT 2.0 のスタイルシートを作成し、この XML データを、トーナメントの結果を示す HTML の表に変換します。リスト 5 はこの XML 文書のフォーマットを示しています。
リスト 5. トーナメントのデータを持つ XML 文書
<?xml version="1.0" encoding="UTF-8"?>
<!-- tourney.xml -->
<bracket>
<title>RSDC Smackdown</title>
<contestants>
<contestant seed="1" image="images/homerSimpson.png">Donuts</contestant>
<contestant seed="2" image="images/caffeine.png">Caffeine</contestant>
<contestant seed="3" image="images/fearlessFreep.png">Fearless Freep</contestant>
<contestant seed="4" image="images/wmd.jpg">Weapons of Mass Destruction</contestant>
<contestant seed="5" image="images/haroldPie.jpg">Pie</contestant>
<contestant seed="6" image="images/adamAnt.png">Adam Ant</contestant>
<contestant seed="7" image="images/georgeWBush.jpg">Misunderestimated</contestant>
<contestant seed="8" image="images/sillyPutty.jpg">Silly Putty</contestant>
<contestant seed="9" image="images/krazyGlue.jpg">Krazy Glue</contestant>
<contestant seed="10" image="images/snoopDogg.png">Biz-Implification</contestant>
<contestant seed="11" image="images/atomAnt.png">Atom Ant</contestant>
<contestant seed="12" image="images/ajaxcan.png">AJAX</contestant>
<contestant seed="13" image="images/darthVader.jpg">Darth Vader</contestant>
<contestant seed="14" image="images/nastyCanasta.png">Nasty Canasta</contestant>
<contestant seed="15" image="images/jcp.png">Java Community Process</contestant>
<contestant seed="16" image="images/andre.png">Andre the Giant</contestant>
</contestants>
<results>
<result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
<result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
<result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
<result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
<result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
<result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
<result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
<result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
<result round="2" firstSeed="1" secondSeed="9" winnerSeed="1"/>
<result round="2" firstSeed="5" secondSeed="4" winnerSeed="5"/>
<result round="2" firstSeed="11" secondSeed="3" winnerSeed="3"/>
<result round="2" firstSeed="10" secondSeed="2" winnerSeed="2"/>
<result round="3" firstSeed="1" secondSeed="5" winnerSeed="1"/>
<result round="3" firstSeed="3" secondSeed="2" winnerSeed="2"/>
<result round="4" firstSeed="1" secondSeed="2" winnerSeed="2"/>
</results>
</bracket> |
<title> 要素の中を見ると、このトーナメントの名前が RSDC Smackdown であることがわかります。この文書は、今年開催された IBM Rational Software Developer Conference のセッションの実際の結果を表しています。
この対戦表 (bracket) は 16 の <contestant> (出場チーム) 要素を含み、それぞれの <contestant> 要素は、名前 (その要素のテキスト) のほかに、シード番号、画像といった属性を持っています。16 チームのトーナメントは 15 回の対戦で構成されます。各対戦は <result> 要素で表現されます。各対戦には 4 つのデータが関連付けられています。これらは、対戦が行われたトーナメントの第何回戦か (round 属性)、2 つの対戦チームのシード番号 (firstSeed 属性と secondSeed 属性に保存されます)、そして勝ったチームのシード番号 ( winnerSeed 属性) の 4 つです。皆さんの課題は、この XML 文書を、結果を示す図 1 のような HTML の表に変換することです。
図 1. HTML の表でのトーナメントの結果
この表は 32 行と 5 列です。この HTML の表を XSLT 1.0 の方法で作成する場合には、1 度に 1 行ずつ作成します (リスト 6)。
Listing 6. 1 度に 1 行ずつ HTML の表を作成する
<!-- Row 1 -->
<tr>
<td style="border: none; border-top: solid; border-right: solid;">
<xsl:text>[1] </xsl:text>
<xsl:value-of select="$contestants[@seed='1']/>
</td>
<td style="border: none;>
</td>
<td style="border: none;>
</td>
<td style="border: none;>
</td>
<td style="border: none;>
</td>
</tr>
<!-- Row 2 -->
. . . |
この方法も可能ですが、これではスタイルシートの維持管理が困難です。各行と列のためのコードをスタイルシート全体で繰り返さなければなりません。この問題の中心は、出力される表に 32 行が必要なことです。その 32 行のそれぞれが、XML 文書の 1 つの要素のデータを含んでいます (<contestant> あるいは <result>)。残念ながら、繰り返し可能な 32 個の要素があるわけではありません。<xsl:for-each select="contestants/contestant|results/result"> を使うことも考えられますが、これらの要素は出力される表の中で必要な順序では現れません。XSLT 1.0 には、このような場合に役立つツールが少ないのです。
コードをリファクタリングすれば、スタイルシートをもっと単純にすることができます。セルのスタイルと内容には一定のパターンがあり、XPath 2.0 と XSLT 2.0 の新しい機能を利用すると、これらのパターンに対して繰り返しを行うことができます。最終的なスタイルシートは、(明らかに不格好な) オリジナル・バージョンよりも約 70% 小さくなります。そうしたスタイルシートを作成する前に、どのようにコードをリファクタリングするのかを調べてみましょう。
コードをリファクタリングする -- 表のセルのスタイルを計算する
コードをリファクタリングするためには、まずスタイル情報を CSS スタイルシートに移動します。図 2 を見るとわかるように、HTML の対戦表には、None と MatchupStart、MatchupMiddle、MatchupEnd、そして Solid という 5 つの異なるセル・スタイルがあります。
図 2. HTML の表でのセルの枠線スタイル
リスト 7 は、この CSS コードの様子を示しています。
リスト 7. CSS スタイルシート
.None { width: 20%; }
.MatchupStart { width: 20%;
border: none; border-top: solid;
border-right: solid; }
.MatchupMiddle { width: 20%;
border: none; border-right: solid; }
.MatchupEnd { width: 20%;
border: none; border-bottom: solid;
border-right: solid; }
.Solid { width: 20%;
border: solid; } |
枠線のスタイルによって、トーナメントの結果が見やすくなります。2 つのセルを結ぶ縦方向の線は、この 2 つのチームが対戦することを示しています。その次の列では、実線の枠線が付いたセルはその対戦の勝者を示しており、枠線のスタイルには次のような明確なパターンがあることがわかります。対戦チームのペアに対して、上側に表示されている対戦チームのさらに上のセルには枠線がなく (スタイルは None)、対戦する 2 チームを表示しているセルには実線の枠線があり (スタイルは Solid)、2 つのチームの間にあるセルには右側に枠線があり (スタイルは MatchupMiddle)、そして下側の対戦チームのさらに下のセルには枠線がありません (スタイルは None)。これは、最初の列とは少し異なっています。最初の列の対戦ペアはお互いに非常に接近しているため、上側の対戦チームのセルには上と右に枠線があり (スタイルは MatchupStart)、下側の対戦チームのセルには下と右に枠線があります (スタイルは MatchupEnd)。
各列で、対戦チームのペアが、(枠線以外にも) セルによってさらに分離されていることに注意してください。列 1 では対戦チーム同士が 1 セル分だけ離れており、列 2 では 3 セル分、列 3 では 7 セル分、列 4 では 15 セル分離れています。この距離 (間隔のセル数) は 2 の累乗マイナス 1 なので、ここには明確なパターンがあります。
表全体としてのパターンを一般化すると、各列には、繰り返しパターンを持つセル・グループがあります。この繰り返しグループのサイズ (適当な表現がないので、列の周期 (period) と呼ぶことにします) は、5 つの列に対して、4、8、16、32、そして 64 です。繰り返しグループはそれぞれ、対戦する 2 チームと、2 チームの間のセル、そして 2 チームの上と下のセルを含んでいます。
各列の中で、2 つの値 (繰り返しグループのサイズを表す: $period と、周期のサイズの 1/4 である $oneQuarter) を使って計算を行います。($period div 4 を 1 つの変数の中に保存すると、コードが簡潔になります。) 表 1 はセルの枠線のスタイルの一般的なルールを示しています。
表 1. HTML の表でのセルの枠線スタイル
| 公式 | 列 1 (周期 4) | 列 2 (周期 8) | 列 3 (周期 16) | 列 4 (周期 32) | 列 5 (周期 64) |
|---|
$row < $oneQuarter or $row > $period - $oneQuarter
| -- |
行 1、 スタイル None
|
行 1-3、 スタイル None
|
行 1-7、 スタイル None
|
行 1-15、 スタイル None
|
$row = $oneQuarter
|
行 1、 スタイル MatchupStart
|
行 2、 スタイル Solid
|
行 4、 スタイル Solid
|
行 8、 スタイル Solid
|
行 16、 スタイル Solid
|
$row > $oneQuarter and $row < $period - $oneQuarter
|
行 2、 スタイル MatchupMiddle
|
行 3-5、 スタイル MatchupMiddle
|
行 5-11、 スタイル MatchupMiddle
|
行 9-23、 スタイル MatchupMiddle
|
行 17-32、 スタイル None
|
$row = $period - $oneQuarter
|
行 3、 スタイル MatchupEnd
|
行 6、 スタイル Solid
|
行 12、 スタイル Solid
|
行 24、 スタイル Solid
| -- |
$row < $oneQuarter or $row > $period - $oneQuarter
|
行 4、 スタイル None
|
行 7-8、 スタイル None
|
行 13-16、 スタイル None
|
行 25-32、 スタイル None
| -- |
これで、表の中の任意のセルのスタイルをスマートに判断する方法ができました。列の番号と行の番号を指定すると、この公式は魔法のように効果を発揮します。
コードをリファクタリングする -- 表のセルの内容を計算する
表の中のセルを作成する際には、当然ながらそのセルに適切な内容を入れる必要があります。ここにも素晴らしいパターンがあります。None あるいは MatchupMiddle というスタイルを持つすべてのセルには、何も内容がありません。したがって、残りの 3 つのスタイルを持つセルを対象にして、適切な内容を判断できればよいということです。
表 1 を見ると、内容を持つセルのプロパティーが $row = $oneQuarter or $row = $period - $oneQuarter であることがわかります。最初の列の場合は、単純に適切な出場チームのシード番号と名前を書くだけです。組み合わせは出場チームに付与されたシード番号を基に行われ、表 2 のような対戦になっています。
表 2. シード番号に基づく組み合わせ
| [1] 対 [16] | | [8] 対 [9] | | [5] 対 [12] | | [4] 対 [13] | | [6] 対 [11] | | [3] 対 [14] | | [7] 対 [10] | | [2] 対 [15] |
この組み合わせは次のようになっています。シード番号が大きい方が強いとすると、最もシード番号が大きいチームは残った中で最もシード番号の小さいチームと必ず対戦し、2 番目に大きいシード番号のチームは残った中で下から 2 番目に小さいシード番号のチームと必ず対戦し、というように対戦が組まれます。シード番号の順が (1、16、8、9、5、12、4、13、6、11、3、14、7、10、2、15) であることから、これらが $seeds のシーケンス値に一致することがわかります。対戦するチームは、この順序で列 1 に表示されます。
出場チームは 1 行おきに現れます。これはつまり、行 1 にはシーケンスの最初のシード番号が入り、行 3 にはシーケンスの 2 番目のシード番号が入り、行 5 にはシーケンスの 3 番目のシード番号が入り、ということです。ここでのパターンは、$row + 1 div 2 = $index です。つまり行番号に 1 を加え、それを 2 で割ると $seeds シーケンスのシード番号のインデックスが得られます。表のセルの内容は、まずシード番号があり、その後に、そのシード番号を持つ出場チームの名前が来ます。
これで、列 1 の内容が処理できました。列 2 から列 5 は、ご想像の通り、もっと複雑です。<contestant> 要素を見る代わりに、15 個ある <result> 要素に表示される、結果を見る必要があります。
列 2 は第 1 回戦 (round 1) の勝者を含んでいます。これはつまり、属性 round="1" を持つ <result> 要素を見る必要があるということです。単純にするために、最初の対戦 (シード番号 1 対シード番号 16) の勝者は最初の <result> 要素に保存され、2 番目の対戦 (シード番号 8 対シード番号 9) の勝者は 2 番目の <result> 要素に保存され、というようにします。列 2 の勝者は、行 2 と 6、10、14、18、22、26、そして 30 に表示されます。パターンを探してみると、行 2 が最初の <result> 要素を使い、行 6 が 2 番目、行 10 が 3 番目の <result> 要素を使っています。この列の場合、行番号に 2 を加えて 4 で割ると、適切な <result round="1"> 要素の位置が得られます。
列 3 と 4 も同じように処理することができます。列 3 の勝者は行 4、12、20、そして 28 に表示されます (ここのパターンでは、4 を加えて 8 で割ります)。列 4 の勝者は行 8 と 24 に表示されます (8 を加えて 16 で割ります)。列 5 は 1 つの対戦チーム、つまりこのトーナメント全体の勝者のみを表示します。もし行 16 にいるとすると、1 つの <result round="4"> 要素から勝者を表示します。
探している値の位置を見つける方法をリファクタリングすると、($row + $oneQuarter) div ($oneQuarter * 2) となります。使用する繰り返しパターンのサイズを次第に大きくすることによって、コードを単純にすることができます。列 1 の場合は、計算される位置はシード番号のシーケンスでのインデックスです。他の列の場合は、計算される位置は適切な <result> 要素の位置です。
これで、表の各セルの内容とスタイルの両方をスマートに決定できるようになりました。行番号と列番号を指定すると、知る必要があるすべての情報を知ることができます。
XPath 2.0 と XSLT 2.0 の能力を利用する
今度は XPath 2.0 と XSLT 2.0 で利用できる新しい手法を使ってスタイルシートを整えます。まず、to 演算子を使います。この表を手続き型のプログラミング言語で作成したとすると、リスト 8 のようになるでしょう。
リスト 8. 手続き型の方法でスタイルシートを扱う
for (int row=1; row<=32; row++)
for (int column=1; column<=5; column++)
// Build each cell in the table here |
XPath 2.0 と XSLT 2.0 では、手続き型言語で使用する for ループを <xsl:for-each> で置き換えることができます。リスト 9 がその方法を示しています。
リスト 9. to 演算子を使って for ループを行う
<xsl:for-each select="1 to 32">
<xsl:variable name="outerIndex" select="."/>
<tr>
<xsl:for-each select="1 to 5"> |
ここでは、いくつか面倒なものに対応する必要があります。まず XPath 1.0 と XSLT 1.0 では、<xsl:for-each> を使うと、繰り返しごとにコンテキストが変わりました。例えば、もし <xsl:for-each select="contestants/contestant> を使ったとすると、コンテキスト・ノードは、各繰り返しでの最新の <contestant> になります。一方、さまざまな整数に対して繰り返しを行う際に to 演算子を使うと、コンテキスト項目 (2.0 ではコンテキスト項目です) は未定義になります。リスト 9 を見るとわかるように、外側の <xsl:for-each> の現在の値は内側の <xsl:for-each> の中では利用できないので、この値を保存しておく必要があります。
しかし事態はもっと深刻になります。もしコンテキスト項目が未定義だとすると、この文書からノードを選択する方法がありません。もし今、行 1、列 1 にいることがわかっていれば、($seeds はグローバル変数なので) 最初の項目を $seeds シーケンスから取得することができます。これは、<contestant seed="1"> 要素を見つける必要があるということです。しかし残念ながら、文書中のどこにもアクセスすることはできません。絶対 XPath 式 (例えば /bracket/contestants/contestant[@seed='1'] など) を使っても、うまくいきません。このことから、関心対象となるノードをグローバル変数として保存する必要があります。リスト 10 は、いつでもどこからでも、必要なときにグローバル変数にアクセスするための方法を示しています。
Listing 10. 必要なノードを保存するグローバル変数
<xsl:variable name="results" select="/bracket/results"/>
<xsl:variable name="contestants" select="/bracket/contestants"/> |
シード番号が 16 の出場チームの名前を知りたい場合の XPath 式は $contestants/contestant[@seed="16"] です。<result> 要素にアクセスする場合も同様です。(第 2 回戦での) 2 回目の対戦の勝者を取得したい場合の式は $results/result[@round="2"][2]/@winnerSeed です。リスト 11 は、HTML の表を生成するために使用できる、さらに 2 つのグローバル変数を示しています。
リスト 11. 他の、便利なグローバル変数
<xsl:variable name="periods" as="xs:integer*">
<xsl:sequence select="(4, 8, 16, 32, 64)"/>
</xsl:variable>
<xsl:variable name="backgroundColors" as="xs:string*">
<xsl:sequence
select="('background: #CCCCCC;', 'background: #9999CC;',
'background: #99CCCC;', 'background: #CC99CC;',
'background: #CCCC99;')"/>
</xsl:variable> |
これらの変数は、各列の周期の値と各列の背景色を保存します。各変数を使うためには、必要な 1 つの値のインデックスとして、単純に列番号を使います。
XPath 2.0 と XSLT 2.0 での、もう 1 つの機能強化が <xsl:function> 要素です。スタイルシートの中に、cellStyle と getResults という 2 つの関数を作成しましょう。最初の関数は各セルの枠線のスタイルを返し、2 番目の関数は、(対戦結果がある場合には) 指定された対戦の結果を返します。両方の関数のパラメーターは、セルの行と列の番号です。リスト 12 は cellStyle 関数のコードを示しています。
リスト12. cellStyle 関数
<xsl:function name="bracket:cellStyle" as="xs:string">
<xsl:param name="row" as="xs:integer"/>
<xsl:param name="column" as="xs:integer"/>
<xsl:variable name="period" as="xs:integer"
select="subsequence($periods, $column, 1)"/>
<xsl:variable name="oneQuarter" as="xs:integer"
select="$period div 4"/>
<xsl:variable name="lastColumn" as="xs:boolean"
select="$oneQuarter = count($contestants/contestant)"/>
<xsl:variable name="position" select="$row mod $period"/>
<xsl:value-of
select="if ($position = $oneQuarter) then
(if ($column = 1) then 'MatchupStart'
else 'Solid')
else if ($position = $period - $oneQuarter) then
(if ($column = 1) then 'MatchupEnd'
else 'Solid')
else if ($lastColumn) then 'None'
else if ($position < $oneQuarter or
$position > $period - $oneQuarter) then 'None'
else 'MatchupMiddle'"/>
</xsl:function> |
セルのスタイルを決定する際には、4 つの変数を使います。$period の値をグローバル変数 $periods から取得します。先ほど触れたように、$oneQuarter は単純に $period div 4 です。ブール値 $lastColumn は単純に、$oneQuarter をトーナメントの対戦チームのカウントと比較します。もし両方の値が等しければ、最後の列を処理していることになります。最後に、変数 $position は、そのパターンでの現在の行の位置を示しています。つまり、この表の行 9 は、列 1 と列 2 の繰り返しグループの最初の行です。そのパターンでの行の位置を使うと、セルのスタイルを決定することができます。
XPath 2.0 と XSLT 2.0 の新機能に if 演算子があります。この演算子には、(括弧に入った) 式があり、その後に then と else が続きます。これらのすべてが <xsl:value-of> の select の属性に入ります。この例では 1 つの式をで、はるかに冗長な <xsl:choose> 要素を置き換えます。この場合のコードは、表の公式に関する説明からわかるように非常に単純です。もしロジックがもっと複雑な場合には、(余分な入力作業が必要ですが) <xsl:choose> を使った方が維持管理しやすいかもしれません。
<xsl:function> の構文に関するいくつかの注意を挙げておきます。まず、その関数に対する新しい名前空間を宣言する必要があります。XPath 式の bracket:cellStyle() を呼び出すと、XSLT 2.0 プロセッサーは、その関数の見つけ方を名前空間で判断することができます。第 2 に、この関数がストリングを返すことを示すために as="xs:string" 属性を使ったことに注意してください。<xsl:value-of> 要素は 5 つのスタイル名の 1 つを返しますが、それがこの関数の出力です。
getResults 関数はもう少し複雑ですが、非常に複雑というわけではありません (リスト 13)。
リスト 13. getResults 関数
<xsl:function name="bracket:getResults" as="xs:string">
<xsl:param name="row" as="xs:integer"/>
<xsl:param name="column" as="xs:integer"/>
<xsl:variable name="period" as="xs:integer"
select="subsequence($periods, $column, 1)"/>
<xsl:variable name="oneQuarter" as="xs:integer"
select="$period div 4"/>
<xsl:variable name="position" select="$row mod $period"/>
<xsl:choose>
<xsl:when test="$position = $oneQuarter
or
$position = $period - $oneQuarter">
<xsl:variable name="round" select="$column - 1"/>
<xsl:variable name="index"
select="($row + $oneQuarter) div ($oneQuarter * 2)"/>
<xsl:variable name="currentSeed"
select="if ($column = 1) then
subsequence($seeds, $index, 1)
else
$results/result[@round=$round][$index]/@winnerSeed"/>
<xsl:choose>
<xsl:when test="string-length(string($currentSeed))">
<xsl:value-of
select="concat('[', $currentSeed, '] ',
$contestants/contestant[@seed=$currentSeed])"/>
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:function> |
パラメーターは、以前と同じです。ここでは、列 1 に対して少し特別な処理を行う必要があります (列 1 は <contestant> 要素のデータを含み、他の列は <result> 要素のデータを含みます)。cellStyle 関数の場合と同じように、$period と $position を計算し、$oneQuarter 変数を使って公式を単純化します。
もしセルが対戦チームを含む場合には、さらに 3 つの変数を計算します。$round 変数は、その列数よりも 1 少なく (列 2 には第 1 回戦の結果が含まれています)、また $index 変数の計算は、先ほど説明した公式に従って行います。
適切なデータを見つけるためには 2 つのステップに従う必要があります。第 1 に、$currentSeed 変数の値を設定します。もしこれが第 1 回戦であれば、新しい subsequence 関数を使って $seeds 変数から値を選択します。第 1 回戦以外の場合は、適当な <result> 要素から winnerSeed 属性を取得します。
第 2 に、列 1 に対して異なる処理が必要な他に、勝者を持たない <result> 要素 (winnerSeed=") を想定する必要があります。もしそうした要素が発生した場合には、改行させない空白 ( ) を返します。XSLT 2.0 でのデータ型の処理方法 (これは今後の記事で取りあげるべき話題です) に合うように、$currentSeed をストリングに変換し、そしてそのストリングの長さをテストする必要があります。もしストリングの長さがゼロなら改行させない空白を返し、それ以外の場合は出場チームのシード番号と名前を返します。
最後に、ここで <xsl:choose> が使われていることに注意してください。XPath 式 の if 文を 1 つ使えば何でもできるのですが、コードは扱いにくくなります。<xsl:choose> を使った方が冗長になりますが、コードは簡潔に、そして理解しやすくなります。
これで必要なグローバル変数と関数を設定できたので、スタイルシートの心臓部分は単純でスマートです (リスト 14)。
リスト 14. スタイルシートの心臓部分
<xsl:for-each select="1 to 32">
<xsl:variable name="outerIndex" select="."/>
<tr>
<xsl:for-each select="1 to 5">
<td style="{subsequence($backgroundColors, ., 1)}"
class="{bracket:cellStyle($outerIndex, .)}">
<xsl:value-of
select="bracket:getResults($outerIndex, .)"/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each> |
2 つのループがあり、これが表の中の 32 の行に対して 5 つの列を作成します。各セルの背景色は現在の列番号に基づいています。cellStyle 関数は枠線のスタイル (class="x") を決定し、また getResults 関数はセルの値を決定します。
スタイルシートを使う
ご想像の通り、このスタイルシートを使うためには XSLT 2.0 が必要です。ここではスタイルシートの全体は示しませんが、プロセッサーが XSLT 2.0 モードで実行するように <xsl:stylesheet version="2.0"> を使う必要があります。私は Michael Kay による Saxon プロセッサーを強くお勧めします (詳細は「参考文献」を参照)。Kay 博士は XSLT 2.0 仕様の編集者であり、Saxon はある面で、この仕様を開発する際のテスト・ケースでした。Saxon を使う場合には、次のコマンドとスタイルシート results-html.xsl を使って XML ファイルを tourney.xml に変換し、そして出力を results.html ファイルに書き出します。
java net.sf.saxon.Transform -o results.html tourney.xml results-html.xsl
このスタイルシートの柔軟性を示すために、この XML ファイルの中の結果のいくつかを取り出し、変換を実行してみます。リスト 15 は <result> 要素がどのように表示されるかを示しています。
リスト 15. 不完全なトーナメント・データを持つ XML 文書
<?xml version="1.0" encoding="UTF-8"?>
<!-- incomplete-tourney.xml -->
<bracket>
. . .
<results>
<result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
<result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
<result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
<result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
<result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
<result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
<result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
<result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
<result round="2" firstSeed=" secondSeed=" winnerSeed="/>
<result round="2" firstSeed=" secondSeed=" winnerSeed="/>
<result round="2" firstSeed=" secondSeed=" winnerSeed="/>
<result round="2" firstSeed=" secondSeed=" winnerSeed="/>
<result round="3" firstSeed=" secondSeed=" winnerSeed="/>
<result round="3" firstSeed=" secondSeed=" winnerSeed="/>
<result round="4" firstSeed=" secondSeed=" winnerSeed="/>
</results>
</bracket> |
リスト 15 は、第 1 回戦終了後のトーナメントの状態を表しています。第 2 回戦、3 回戦、4 回戦の <result> 要素は、どれも空の winnerSeed 属性を持っています。それにもかかわらず、このスタイルシートは適切な対戦表を生成します (図 3)。
図 3. 不完全なトーナメントの対戦表
まとめ
この記事では、XPath 2.0 と XSLT 2.0 が持つ、多くの新機能を説明しました。サンプル・アプリケーションでは、扱いにくいスタイルシートをリファクタリングし、ずっと小さく維持管理しやすいコードにしました。作業の一部はパターンを見つけるためのコードの分析に費やしましたが、もし to 演算子や <xsl:function>、item のシーケンスなどがなかったとしたら、これほど大幅にスタイルシートを整理することは、はるかに困難だったはずです。
最も重要なこととして、ここでは任意の数の対戦チームを処理できる汎用の関数を作成しました。例えば 32 チームのトーナメントを処理できるようにスタイルシートを変更するためには、$seeds と $periods のシーケンスを変更する必要があります。また、select="1 to 5" を select="1 to $rounds" で置き換える必要があります ($rounds はトーナメントの対戦回数を表します)。もちろん、最もスマートなソリューションは、対戦回数と、シード番号のシーケンス (32 チームの対戦では、1 と 32 が対戦し、2 と 31 が対戦し、など)、そしてさまざまな列の周期などを含めた汎用の対戦表に対して値を計算する XSLT 2.0 関数を作成することです。これについては読者の演習課題とすることにします。
問題の核心は、表の 32 行に対して繰り返しを行う必要があるにもかかわらず、XSLT 1.0 はそのための現実的な方法を何も提供していないという点です。この問題は、XPath 2.0 と XSLT 2.0 の新機能によって解決することができます
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| XML, XSLT, and HTML code samples | x-xslt20xpath20/samples.zip | 12KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
- Michael Kay による Saxon XSLT 2.0 プロセッサーを SourceForge で見つけてください。
- Altova の Web サイトの Altova XML のページで Altova XSLT プロセッサーの情報を入手し、このプロセッサーを試してみてください。Altova は XMLSpy などの人気製品のメーカーであり、彼らの XSLT プロセッサーを無料で提供しています。このプロセッサーは XSLT 1.0 と XSLT 2.0、そして XQuery 1.0 をサポートしています。これはオープン・ソースではありませんが、このライセンスでは、この XSLT エンジンを製品の中に組みこむことを許可しています。
- 皆さんの次期開発プロジェクトを IBM trial software を使って構築してください。developerWorks から直接ダウンロードすることができます。
議論するために
著者について  | 
|  | Doug Tidwell は IBM の Software Group Strategy 組織の技術エバンジェリストです。彼は現在、SCA (Service Component Architecture) や SDO (Service Data Objects)、XForms などの新興技術を中心とした業務を行っています。彼は O'Reilly 刊の 『XSLT』の著者であり、この第 2 版を皆さんの近くの書店から予約注文することができます。彼は 1997年の最初の XML カンファレンスの講演者の 1 人でもあり、(非常に大ざっぱですが) 20 年近くマークアップ言語に取り組んでいます。現在は、combo meal (セット・メニュー) の発明者であるイギリスのファーストフードの大物、William "Add-a-Piece" Thackeray に関する本を執筆中です。彼はノースキャロライナ州の Chapel Hill に、彼の妻と娘、そして犬と住んでいます。 |
記事の評価
|